diff --git a/composer.json b/composer.json
index 8a78ac5..15d7c13 100644
--- a/composer.json
+++ b/composer.json
@@ -3,8 +3,21 @@
   "description": "Drupal is an open source content management platform powering millions of websites and applications.",
   "type": "drupal-core",
   "license": "GPL-2.0+",
+  "repositories": [
+    {
+      "type":"git",
+      "url": "https://github.com/larowlan/MinkGoutteDriver.git"
+    },
+    {
+      "type":"git",
+      "url": "https://github.com/larowlan/guzzle.git"
+    }
+  ],
   "require": {
     "php": ">=5.4.2",
+    "behat/mink": "~1.5",
+    "behat/mink-goutte-driver": "dev-master",
+    "fabpot/goutte": "dev-master",
     "sdboyer/gliph": "0.1.*",
     "symfony/class-loader": "2.4.*",
     "symfony/css-selector": "2.4.*",
@@ -19,7 +32,7 @@
     "twig/twig": "1.15.*",
     "doctrine/common": "dev-master#a45d110f71c323e29f41eb0696fa230e3fa1b1b5",
     "doctrine/annotations": "dev-master#463d926a8dcc49271cb7db5a08364a70ed6e3cd3",
-    "guzzlehttp/guzzle": "4.1.*",
+    "guzzlehttp/guzzle": "dev-master",
     "kriswallsmith/assetic": "1.1.*@alpha",
     "symfony-cmf/routing": "1.1.*@alpha",
     "easyrdf/easyrdf": "0.8.*",
diff --git a/composer.lock b/composer.lock
index e99a1b9..a3192aa 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1,12 +1,166 @@
 {
     "_readme": [
         "This file locks the dependencies of your project to a known state",
-        "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
-        "This file is @generated automatically"
+        "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
     ],
     "hash": "d89a37ea785ca09523298ff00ade2eca",
     "packages": [
         {
+            "name": "behat/mink",
+            "version": "v1.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Behat/Mink.git",
+                "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Behat/Mink/zipball/0769e6d9726c140a54dbf827a438c0f9912749fe",
+                "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe",
+                "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-develop": "1.5.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": "2013-04-13 23:39:27"
+        },
+        {
+            "name": "behat/mink-browserkit-driver",
+            "version": "v1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Behat/MinkBrowserKitDriver.git",
+                "reference": "63960c8fcad4529faad1ff33e950217980baa64c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Behat/MinkBrowserKitDriver/zipball/63960c8fcad4529faad1ff33e950217980baa64c",
+                "reference": "63960c8fcad4529faad1ff33e950217980baa64c",
+                "shasum": ""
+            },
+            "require": {
+                "behat/mink": "~1.5.0",
+                "php": ">=5.3.1",
+                "symfony/browser-kit": "~2.0",
+                "symfony/dom-crawler": "~2.0"
+            },
+            "require-dev": {
+                "silex/silex": "@dev"
+            },
+            "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": "Symfony2 BrowserKit driver for Mink framework",
+            "homepage": "http://mink.behat.org/",
+            "keywords": [
+                "Mink",
+                "Symfony2",
+                "browser",
+                "testing"
+            ],
+            "time": "2013-04-13 23:46:30"
+        },
+        {
+            "name": "behat/mink-goutte-driver",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/larowlan/MinkGoutteDriver.git",
+                "reference": "488f7f02b1e907888f4b156b635693daf51d760c"
+            },
+            "require": {
+                "behat/mink": "~1.5@dev",
+                "behat/mink-browserkit-driver": "~1.1@dev",
+                "fabpot/goutte": "dev-master",
+                "php": ">=5.4"
+            },
+            "type": "mink-driver",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Behat\\Mink\\Driver": "src/"
+                }
+            },
+            "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-06-04 04:35:02"
+        },
+        {
             "name": "doctrine/annotations",
             "version": "dev-master",
             "source": {
@@ -458,20 +612,65 @@
             "time": "2013-12-30 22:31:37"
         },
         {
-            "name": "guzzlehttp/guzzle",
-            "version": "4.1.0",
+            "name": "fabpot/goutte",
+            "version": "dev-master",
             "source": {
                 "type": "git",
-                "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "85a0ba7de064493c928a8bcdc5eef01e0bde9953"
+                "url": "https://github.com/fabpot/Goutte.git",
+                "reference": "104ef44a9fc8a8dfe66fef5f8e8deb4567f3dc7c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/85a0ba7de064493c928a8bcdc5eef01e0bde9953",
-                "reference": "85a0ba7de064493c928a8bcdc5eef01e0bde9953",
+                "url": "https://api.github.com/repos/fabpot/Goutte/zipball/104ef44a9fc8a8dfe66fef5f8e8deb4567f3dc7c",
+                "reference": "104ef44a9fc8a8dfe66fef5f8e8deb4567f3dc7c",
                 "shasum": ""
             },
             "require": {
+                "guzzlehttp/guzzle": "4.*",
+                "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-0": {
+                    "Goutte": "."
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com",
+                    "homepage": "http://fabien.potencier.org",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "A simple PHP Web Scraper",
+            "homepage": "https://github.com/fabpot/Goutte",
+            "keywords": [
+                "scraper"
+            ],
+            "time": "2014-05-13 05:05:35"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/larowlan/guzzle.git",
+                "reference": "56458c2819e2fe67d175bc11e9b9e472b1b6035f"
+            },
+            "require": {
                 "ext-json": "*",
                 "guzzlehttp/streams": "~1.0",
                 "php": ">=5.4.0"
@@ -487,7 +686,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "4.0.x-dev"
+                    "dev-master": "4.1.x-dev"
                 }
             },
             "autoload": {
@@ -498,7 +697,6 @@
                     "src/functions.php"
                 ]
             },
-            "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "MIT"
             ],
@@ -512,15 +710,15 @@
             "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
             "homepage": "http://guzzlephp.org/",
             "keywords": [
+                "HTTP client",
                 "client",
                 "curl",
                 "framework",
                 "http",
-                "http client",
                 "rest",
                 "web service"
             ],
-            "time": "2014-05-28 05:13:19"
+            "time": "2014-06-04 04:09:52"
         },
         {
             "name": "guzzlehttp/streams",
@@ -1434,6 +1632,63 @@
             "time": "2013-10-14 15:32:46"
         },
         {
+            "name": "symfony/browser-kit",
+            "version": "v2.5.0",
+            "target-dir": "Symfony/Component/BrowserKit",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/BrowserKit.git",
+                "reference": "cc1716dafa04277a0c987aee5bce8c3cf8939de3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/cc1716dafa04277a0c987aee5bce8c3cf8939de3",
+                "reference": "cc1716dafa04277a0c987aee5bce8c3cf8939de3",
+                "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.5-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Symfony\\Component\\BrowserKit\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com",
+                    "homepage": "http://fabien.potencier.org",
+                    "role": "Lead Developer"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony BrowserKit Component",
+            "homepage": "http://symfony.com",
+            "time": "2014-05-12 09:28:39"
+        },
+        {
             "name": "symfony/class-loader",
             "version": "v2.4.1",
             "target-dir": "Symfony/Component/ClassLoader",
@@ -1650,6 +1905,61 @@
             "time": "2014-01-01 09:02:49"
         },
         {
+            "name": "symfony/dom-crawler",
+            "version": "v2.5.0",
+            "target-dir": "Symfony/Component/DomCrawler",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/DomCrawler.git",
+                "reference": "6cda499120860286fe3d3d2fd631dacb7ae17f92"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/6cda499120860286fe3d3d2fd631dacb7ae17f92",
+                "reference": "6cda499120860286fe3d3d2fd631dacb7ae17f92",
+                "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.5-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Symfony\\Component\\DomCrawler\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com",
+                    "homepage": "http://fabien.potencier.org",
+                    "role": "Lead Developer"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony DomCrawler Component",
+            "homepage": "http://symfony.com",
+            "time": "2014-05-31 02:02:56"
+        },
+        {
             "name": "symfony/event-dispatcher",
             "version": "v2.4.1",
             "target-dir": "Symfony/Component/EventDispatcher",
@@ -2401,9 +2711,12 @@
     ],
     "minimum-stability": "stable",
     "stability-flags": {
+        "behat/mink-goutte-driver": 20,
+        "fabpot/goutte": 20,
         "symfony/yaml": 20,
         "doctrine/common": 20,
         "doctrine/annotations": 20,
+        "guzzlehttp/guzzle": 20,
         "kriswallsmith/assetic": 15,
         "symfony-cmf/routing": 15,
         "phpunit/phpunit-mock-objects": 20
diff --git a/core/modules/action/src/Tests/ActionUninstallTest.php b/core/modules/action/src/Tests/ActionUninstallTest.php
index f43bc0c..282e45b 100644
--- a/core/modules/action/src/Tests/ActionUninstallTest.php
+++ b/core/modules/action/src/Tests/ActionUninstallTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\action\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests that uninstalling actions does not remove other module's actions.
@@ -15,7 +15,7 @@
  * @group action
  * @see \Drupal\action\Plugin\views\field\BulkForm
  */
-class ActionUninstallTest extends WebTestBase {
+class ActionUninstallTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/action/src/Tests/BulkFormTest.php b/core/modules/action/src/Tests/BulkFormTest.php
index 024780b..e4c4496 100644
--- a/core/modules/action/src/Tests/BulkFormTest.php
+++ b/core/modules/action/src/Tests/BulkFormTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\action\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 use Drupal\views\Views;
 
 /**
@@ -16,7 +16,7 @@
  * @group action
  * @see \Drupal\action\Plugin\views\field\BulkForm
  */
-class BulkFormTest extends WebTestBase {
+class BulkFormTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -116,7 +116,7 @@ public function testBulkForm() {
     // Check the default title.
     $this->drupalGet('test_bulk_form');
     $result = $this->xpath('//label[@for="edit-action"]');
-    $this->assertEqual('With selection', (string) $result[0]);
+    $this->assertEqual('With selection', $result[0]->getText());
 
     // Setup up a different bulk form title.
     $view = Views::getView('test_bulk_form');
@@ -126,7 +126,7 @@ public function testBulkForm() {
 
     $this->drupalGet('test_bulk_form');
     $result = $this->xpath('//label[@for="edit-action"]');
-    $this->assertEqual('Test title', (string) $result[0]);
+    $this->assertEqual('Test title', (string) $result[0]->getText());
   }
 
 }
diff --git a/core/modules/action/src/Tests/ConfigurationTest.php b/core/modules/action/src/Tests/ConfigurationTest.php
index 512d075..fc0af8f 100644
--- a/core/modules/action/src/Tests/ConfigurationTest.php
+++ b/core/modules/action/src/Tests/ConfigurationTest.php
@@ -8,7 +8,7 @@
 namespace Drupal\action\Tests;
 
 use Drupal\Component\Utility\Crypt;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests complex actions configuration by adding, editing, and deleting a
@@ -16,7 +16,7 @@
  *
  * @group action
  */
-class ConfigurationTest extends WebTestBase {
+class ConfigurationTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockAdminThemeTest.php b/core/modules/block/src/Tests/BlockAdminThemeTest.php
index 20b892c..e55c47b 100644
--- a/core/modules/block/src/Tests/BlockAdminThemeTest.php
+++ b/core/modules/block/src/Tests/BlockAdminThemeTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests the block system with admin themes.
  *
  * @group block
  */
-class BlockAdminThemeTest extends WebTestBase {
+class BlockAdminThemeTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockCacheTest.php b/core/modules/block/src/Tests/BlockCacheTest.php
index 635a03b..bc1ab74 100644
--- a/core/modules/block/src/Tests/BlockCacheTest.php
+++ b/core/modules/block/src/Tests/BlockCacheTest.php
@@ -8,14 +8,14 @@
 namespace Drupal\block\Tests;
 
 use Drupal\Core\Cache\Cache;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests block caching.
  *
  * @group block
  */
-class BlockCacheTest extends WebTestBase {
+class BlockCacheTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockHiddenRegionTest.php b/core/modules/block/src/Tests/BlockHiddenRegionTest.php
index 3c7b238..21e709d 100644
--- a/core/modules/block/src/Tests/BlockHiddenRegionTest.php
+++ b/core/modules/block/src/Tests/BlockHiddenRegionTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests that a newly enabled theme does not inherit blocks to its hidden
@@ -15,7 +15,7 @@
  *
  * @group block
  */
-class BlockHiddenRegionTest extends WebTestBase {
+class BlockHiddenRegionTest extends BrowserTestBase {
 
   /**
    * An administrative user to configure the test environment.
diff --git a/core/modules/block/src/Tests/BlockHookOperationTest.php b/core/modules/block/src/Tests/BlockHookOperationTest.php
index 8baf88a..12a426e 100644
--- a/core/modules/block/src/Tests/BlockHookOperationTest.php
+++ b/core/modules/block/src/Tests/BlockHookOperationTest.php
@@ -8,14 +8,14 @@
 namespace Drupal\block\Tests;
 
 use Drupal\Component\Utility\Unicode;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Implement hook entity operations alter.
  *
  * @group block
  */
-class BlockHookOperationTest extends WebTestBase {
+class BlockHookOperationTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockHtmlTest.php b/core/modules/block/src/Tests/BlockHtmlTest.php
index 5e508e5..0e90b75 100644
--- a/core/modules/block/src/Tests/BlockHtmlTest.php
+++ b/core/modules/block/src/Tests/BlockHtmlTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests block HTML ID validity.
  *
  * @group block
  */
-class BlockHtmlTest extends WebTestBase {
+class BlockHtmlTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockInvalidRegionTest.php b/core/modules/block/src/Tests/BlockInvalidRegionTest.php
index ace9b01..1efce3a 100644
--- a/core/modules/block/src/Tests/BlockInvalidRegionTest.php
+++ b/core/modules/block/src/Tests/BlockInvalidRegionTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests that an active block assigned to a non-existing region triggers the
@@ -15,7 +15,7 @@
  *
  * @group block
  */
-class BlockInvalidRegionTest extends WebTestBase {
+class BlockInvalidRegionTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockLanguageCacheTest.php b/core/modules/block/src/Tests/BlockLanguageCacheTest.php
index f9e3bbc..35dd170 100644
--- a/core/modules/block/src/Tests/BlockLanguageCacheTest.php
+++ b/core/modules/block/src/Tests/BlockLanguageCacheTest.php
@@ -9,14 +9,14 @@
 
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Language\Language;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests display of menu blocks with multiple languages.
  *
  * @group block
  */
-class BlockLanguageCacheTest extends WebTestBase {
+class BlockLanguageCacheTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockLanguageTest.php b/core/modules/block/src/Tests/BlockLanguageTest.php
index 527cbf8..ab03311 100644
--- a/core/modules/block/src/Tests/BlockLanguageTest.php
+++ b/core/modules/block/src/Tests/BlockLanguageTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests if a block can be configure to be only visibile on a particular
@@ -15,7 +15,7 @@
  *
  * @group block
  */
-class BlockLanguageTest extends WebTestBase {
+class BlockLanguageTest extends BrowserTestBase {
 
   /**
    * An administrative user to configure the test environment.
diff --git a/core/modules/block/src/Tests/BlockPreprocessUnitTest.php b/core/modules/block/src/Tests/BlockPreprocessUnitTest.php
index 9118e21..8112c52 100644
--- a/core/modules/block/src/Tests/BlockPreprocessUnitTest.php
+++ b/core/modules/block/src/Tests/BlockPreprocessUnitTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests the template_preprocess_block() function.
  *
  * @group block
  */
-class BlockPreprocessUnitTest extends WebTestBase {
+class BlockPreprocessUnitTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockRenderOrderTest.php b/core/modules/block/src/Tests/BlockRenderOrderTest.php
index bf7d0e7..6f964c5 100644
--- a/core/modules/block/src/Tests/BlockRenderOrderTest.php
+++ b/core/modules/block/src/Tests/BlockRenderOrderTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests blocks are being rendered in order by weight.
  *
  * @group block
  */
-class BlockRenderOrderTest extends WebTestBase {
+class BlockRenderOrderTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockTemplateSuggestionsUnitTest.php b/core/modules/block/src/Tests/BlockTemplateSuggestionsUnitTest.php
index 0a041ab..6e5b134 100644
--- a/core/modules/block/src/Tests/BlockTemplateSuggestionsUnitTest.php
+++ b/core/modules/block/src/Tests/BlockTemplateSuggestionsUnitTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests the block_theme_suggestions_block() function.
  *
  * @group block
  */
-class BlockTemplateSuggestionsUnitTest extends WebTestBase {
+class BlockTemplateSuggestionsUnitTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockTestBase.php b/core/modules/block/src/Tests/BlockTestBase.php
index 3ee504f..db3a481 100644
--- a/core/modules/block/src/Tests/BlockTestBase.php
+++ b/core/modules/block/src/Tests/BlockTestBase.php
@@ -7,12 +7,12 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Provides setup and helper methods for block module tests.
  */
-abstract class BlockTestBase extends WebTestBase {
+abstract class BlockTestBase extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockTitleXSSTest.php b/core/modules/block/src/Tests/BlockTitleXSSTest.php
index c295857..dbca39f 100644
--- a/core/modules/block/src/Tests/BlockTitleXSSTest.php
+++ b/core/modules/block/src/Tests/BlockTitleXSSTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests block XSS in title.
  *
  * @group block
  */
-class BlockTitleXSSTest extends WebTestBase {
+class BlockTitleXSSTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/BlockUiTest.php b/core/modules/block/src/Tests/BlockUiTest.php
index d2408da..89929b7 100644
--- a/core/modules/block/src/Tests/BlockUiTest.php
+++ b/core/modules/block/src/Tests/BlockUiTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests that the block configuration UI exists and stores data correctly.
  *
  * @group block
  */
-class BlockUiTest extends WebTestBase {
+class BlockUiTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -100,8 +100,8 @@ function testBlockAdminUiPage() {
     foreach ($this->blockValues as $delta => $values) {
       $block = $this->blocks[$delta];
       $label = $block->label();
-      $element = $this->xpath('//*[@id="blocks"]/tbody/tr[' . $values['tr'] . ']/td[1]/text()');
-      $this->assertTrue((string) $element[0] == $label, 'The "' . $label . '" block title is set inside the ' . $values['settings']['region'] . ' region.');
+      $element = $this->xpath('//*[@id="blocks"]/tbody/tr[' . $values['tr'] . ']/td[1]');
+      $this->assertTrue($element[0]->getText() == $label, 'The "' . $label . '" block title is set inside the ' . $values['settings']['region'] . ' region.');
       // Look for a test block region select form element.
       $this->assertField('blocks[' . $values['settings']['id'] . '][region]', 'The block "' . $values['label'] . '" has a region assignment field.');
       // Move the test block to the header region.
diff --git a/core/modules/block/src/Tests/NewDefaultThemeBlocksTest.php b/core/modules/block/src/Tests/NewDefaultThemeBlocksTest.php
index cc4f911..d0385ac 100644
--- a/core/modules/block/src/Tests/NewDefaultThemeBlocksTest.php
+++ b/core/modules/block/src/Tests/NewDefaultThemeBlocksTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests that the new default theme gets blocks.
  *
  * @group block
  */
-class NewDefaultThemeBlocksTest extends WebTestBase {
+class NewDefaultThemeBlocksTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php b/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php
index f7ffd37..08df8d2 100644
--- a/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php
+++ b/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests the block administration page for a non-default theme.
  *
  * @group block
  */
-class NonDefaultBlockAdminTest extends WebTestBase {
+class NonDefaultBlockAdminTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/core/modules/block_content/src/Tests/BlockContentCreationTest.php b/core/modules/block_content/src/Tests/BlockContentCreationTest.php
index 9b3d914..89b2007 100644
--- a/core/modules/block_content/src/Tests/BlockContentCreationTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentCreationTest.php
@@ -68,7 +68,7 @@ public function testBlockContentCreation() {
 
     // Go to the configure page and verify that the new view mode is correct.
     $this->drupalGet('admin/structure/block/manage/testblock');
-    $this->assertFieldByXPath('//select[@name="settings[block_content][view_mode]"]/option[@selected="selected"]/@value', 'test_view_mode', 'View mode changed to Test View Mode');
+    $this->assertFieldByXPath('//select[@name="settings[block_content][view_mode]"]/option[@selected="selected"]', 'test_view_mode', 'View mode changed to Test View Mode');
 
     // Test the available view mode options.
     $this->assertOption('edit-settings-block-content-view-mode', 'default', 'The default view mode is available.');
diff --git a/core/modules/block_content/src/Tests/BlockContentTestBase.php b/core/modules/block_content/src/Tests/BlockContentTestBase.php
index 4f77de4..b22df8b 100644
--- a/core/modules/block_content/src/Tests/BlockContentTestBase.php
+++ b/core/modules/block_content/src/Tests/BlockContentTestBase.php
@@ -7,12 +7,12 @@
 
 namespace Drupal\block_content\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Sets up page and article content types.
  */
-abstract class BlockContentTestBase extends WebTestBase {
+abstract class BlockContentTestBase extends BrowserTestBase {
 
   /**
    * Profile to use.
diff --git a/core/modules/book/src/Tests/BookTest.php b/core/modules/book/src/Tests/BookTest.php
index b72a046..a25f867 100644
--- a/core/modules/book/src/Tests/BookTest.php
+++ b/core/modules/book/src/Tests/BookTest.php
@@ -8,14 +8,14 @@
 namespace Drupal\book\Tests;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Create a book, add pages, and test book interface.
  *
  * @group book
  */
-class BookTest extends WebTestBase {
+class BookTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -209,7 +209,7 @@ function checkBookNode(EntityInterface $node, $nodes, $previous = FALSE, $up = F
     $links = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
     $got_breadcrumb = array();
     foreach ($links as $link) {
-      $got_breadcrumb[] = (string) $link['href'];
+      $got_breadcrumb[] = $link->getAttribute('href');
     }
 
     // Compare expected and got breadcrumbs.
diff --git a/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php b/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
index 8dfe587..fd78bbb 100644
--- a/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
+++ b/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
@@ -9,14 +9,14 @@
 
 use Drupal\editor\Entity\Editor;
 use Drupal\filter\FilterFormatInterface;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests administration of CKEditor.
  *
  * @group ckeditor
  */
-class CKEditorAdminTest extends WebTestBase {
+class CKEditorAdminTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -61,9 +61,9 @@ function testExistingFormat() {
     $this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
     $this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
     $this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
-    $this->assertTrue(((string) $options[0]) === 'None', 'Option 1 in the Text Editor select is "None".');
-    $this->assertTrue(((string) $options[1]) === 'CKEditor', 'Option 2 in the Text Editor select is "CKEditor".');
-    $this->assertTrue(((string) $options[0]['selected']) === 'selected', 'Option 1 ("None") is selected.');
+    $this->assertTrue(($options[0]->getText()) === 'None', 'Option 1 in the Text Editor select is "None".');
+    $this->assertTrue(($options[1]->getText()) === 'CKEditor', 'Option 2 in the Text Editor select is "CKEditor".');
+    $this->assertTrue(($options[0]->getAttribute('selected')) === 'selected', 'Option 1 ("None") is selected.');
 
     // Select the "CKEditor" editor and click the "Save configuration" button.
     $edit = array(
diff --git a/core/modules/config/src/Tests/ConfigSingleImportExportTest.php b/core/modules/config/src/Tests/ConfigSingleImportExportTest.php
index 5609f8f..17d40ac 100644
--- a/core/modules/config/src/Tests/ConfigSingleImportExportTest.php
+++ b/core/modules/config/src/Tests/ConfigSingleImportExportTest.php
@@ -8,14 +8,14 @@
 namespace Drupal\config\Tests;
 
 use Drupal\Component\Serialization\Yaml;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests the user interface for importing/exporting a single configuration.
  *
  * @group config
  */
-class ConfigSingleImportExportTest extends WebTestBase {
+class ConfigSingleImportExportTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -130,13 +130,14 @@ public function testExport() {
     $element = $this->xpath('//select[@name="config_name"]');
     $options = $this->getAllOptions($element[0]);
     $expected_options = array('system.site', 'user.settings');
-    foreach ($options as &$option) {
-      $option = (string) $option;
+    $option_values = array();
+    foreach ($options as $option) {
+      $option_values[] = $option->getAttribute('value');
     }
-    $this->assertIdentical($expected_options, array_intersect($expected_options, $options), 'The expected configuration files are listed.');
+    $this->assertIdentical($expected_options, array_intersect($expected_options, $option_values), 'The expected configuration files are listed.');
 
     $this->drupalGet('admin/config/development/configuration/single/export/system.simple/system.image');
-    $this->assertFieldByXPath('//textarea[@name="export"]', "toolkit: gd\n", 'The expected system configuration is displayed.');
+    $this->assertTextArea('export', "toolkit: gd\n", 'The expected system configuration is displayed.');
 
     $this->drupalGet('admin/config/development/configuration/single/export/date_format');
     $this->assertFieldByXPath('//select[@name="config_type"]//option[@selected="selected"]', t('Date format'), 'The date format entity type is selected when specified in the URL.');
@@ -146,7 +147,41 @@ public function testExport() {
 
     $fallback_date = \Drupal::entityManager()->getStorage('date_format')->load('fallback');
     $data = Yaml::encode($fallback_date->toArray());
-    $this->assertFieldByXPath('//textarea[@name="export"]', $data, 'The fallback date format config entity export code is displayed.');
+    $this->assertTextArea('export', $data, 'The fallback date format config entity export code is displayed.');
   }
 
+  /**
+   * Assert textarea matches an expected value.
+   *
+   * BrowserKitDriver expects a form to have a submit button. Configuration
+   * export does not, therefore this is work around to testing textarea value.
+   *
+   * @param string $name
+   *   Name of textarea field.
+   * @param string $value
+   *   Expected value.
+   * @param string $message
+   *   A message to display with the assertion.
+   *
+   * @return bool
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertTextArea($name, $value, $message) {
+    // @todo Remove this hack, as may want to use another driver for example.
+    $xpath = '//textarea[@name="' . $name . '"]/text()';
+    $xpath = $this->buildXPathQuery($xpath);
+    $elements = $this->getSession()->getPage()->findAll('xpath', $xpath);
+    if (empty($elements)) {
+      return $this->assert(FALSE, $message);
+    }
+    $reflection = new \ReflectionClass($this->getSession()->getDriver());
+    $method = $reflection->getMethod('getCrawler');
+    $method->setAccessible(TRUE);
+    $crawler = $method->invoke($this->getSession()->getDriver());
+    $text = '';
+    foreach ($elements as $element) {
+      $text .= $crawler->filterXPath($element->getXpath())->eq(0)->text();
+    }
+    return $this->assertEqual($value, $text, $message);
+  }
 }
diff --git a/core/modules/dblog/src/Tests/DbLogTest.php b/core/modules/dblog/src/Tests/DbLogTest.php
index 683479f..ecd8e33 100644
--- a/core/modules/dblog/src/Tests/DbLogTest.php
+++ b/core/modules/dblog/src/Tests/DbLogTest.php
@@ -9,7 +9,8 @@
 
 use Drupal\Component\Utility\Xss;
 use Drupal\dblog\Controller\DbLogController;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\MinkNodeElementDecorator;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Generate events and verify dblog entries; verify user access to log reports
@@ -17,7 +18,7 @@
  *
  * @group dblog
  */
-class DbLogTest extends WebTestBase {
+class DbLogTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -278,15 +279,13 @@ private function doUser() {
     if ($links = $this->xpath('//a[text()="' . html_entity_decode($message_text) . '"]')) {
       // Found link with the message text.
       $links = array_shift($links);
-      foreach ($links->attributes() as $attr => $value) {
-        if ($attr == 'href') {
+      if ($links->hasAttribute('href')) {
+        $value = $links->getAttribute('href');
           // Extract link to details page.
-          $link = drupal_substr($value, strpos($value, 'admin/reports/dblog/event/'));
-          $this->drupalGet($link);
-          // Check for full message text on the details page.
-          $this->assertRaw($message, 'DBLog event details was found: [delete user]');
-          break;
-        }
+        $link = drupal_substr($value, strpos($value, 'admin/reports/dblog/event/'));
+        $this->drupalGet($link);
+        // Check for full message text on the details page.
+        $this->assertRaw($message, 'DBLog event details was found: [delete user]');
       }
     }
     $this->assertTrue($link, 'DBLog event was recorded: [delete user]');
@@ -605,11 +604,11 @@ protected function getSeverityConstant($class) {
    * @return string
    *   Extracted text.
    */
-  protected function asText(\SimpleXMLElement $element) {
+  protected function asText(MinkNodeElementDecorator $element) {
     if (!is_object($element)) {
       return $this->fail('The element is not an element.');
     }
-    return trim(html_entity_decode(strip_tags($element->asXML())));
+    return trim(html_entity_decode(strip_tags($element->getHtml())));
   }
 
   /**
diff --git a/core/modules/dblog/src/Tests/Rest/DbLogResourceTest.php b/core/modules/dblog/src/Tests/Rest/DbLogResourceTest.php
index f216ee6..a215ebf 100644
--- a/core/modules/dblog/src/Tests/Rest/DbLogResourceTest.php
+++ b/core/modules/dblog/src/Tests/Rest/DbLogResourceTest.php
@@ -47,7 +47,7 @@ public function testWatchdog() {
 
     $response = $this->httpRequest("dblog/$id", 'GET', NULL, $this->defaultMimeType);
     $this->assertResponse(200);
-    $this->assertHeader('content-type', $this->defaultMimeType);
+    $this->assertHeader('Content-Type', $this->defaultMimeType);
     $log = Json::decode($response);
     $this->assertEqual($log['wid'], $id, 'Log ID is correct.');
     $this->assertEqual($log['type'], 'rest', 'Type of log message is correct.');
diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
index 348bf4a..13015dd 100644
--- a/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
+++ b/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\entity_reference\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests for the administrative UI.
  *
  * @group entity_reference
  */
-class EntityReferenceAdminTest extends WebTestBase {
+class EntityReferenceAdminTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -128,23 +128,17 @@ protected function assertFieldSelectOptions($name, array $expected_options) {
   /**
    * Extracts all options from a select element.
    *
-   * @param \SimpleXMLElement $element
+   * @param $element
    *   The select element field information.
    *
    * @return array
    *   An array of option values as strings.
    */
-  protected function getAllOptionsList(\SimpleXMLElement $element) {
+  protected function getAllOptionsList($element) {
     $options = array();
-    // Add all options items.
-    foreach ($element->option as $option) {
-      $options[] = (string) $option['value'];
+    foreach ($this->getAllOptions($element) as $opt_element) {
+      $options[] = $opt_element->getAttribute('value');
     }
-
-    if (isset($element->optgroup)) {
-      $options += $this->getAllOptionsList($element->optgroup);
-    }
-
     return $options;
   }
 
diff --git a/core/modules/filter/src/Tests/FilterAdminTest.php b/core/modules/filter/src/Tests/FilterAdminTest.php
index e438162..ac2033a 100644
--- a/core/modules/filter/src/Tests/FilterAdminTest.php
+++ b/core/modules/filter/src/Tests/FilterAdminTest.php
@@ -8,14 +8,14 @@
 namespace Drupal\filter\Tests;
 
 use Drupal\Component\Utility\String;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Thoroughly test the administrative interface of the filter module.
  *
  * @group filter
  */
-class FilterAdminTest extends WebTestBase {
+class FilterAdminTest extends BrowserTestBase {
 
   /**
    * {@inheritdoc}
@@ -244,9 +244,9 @@ function testFilterAdmin() {
     $format = entity_load('filter_format', $edit['format']);
     $this->assertNotNull($format, 'Format found in database.');
     $this->drupalGet('admin/config/content/formats/manage/' . $format->format);
-    $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', '', 'Role found.');
-    $this->assertFieldByName('filters[' . $second_filter . '][status]', '', 'Line break filter found.');
-    $this->assertFieldByName('filters[' . $first_filter . '][status]', '', 'Url filter found.');
+    $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', DRUPAL_AUTHENTICATED_RID, 'Role found.');
+    $this->assertFieldByName('filters[' . $second_filter . '][status]', TRUE, 'Line break filter found.');
+    $this->assertFieldByName('filters[' . $first_filter . '][status]', TRUE, 'Url filter found.');
 
     // Disable new filter.
     $this->drupalPostForm('admin/config/content/formats/manage/' . $format->format . '/disable', array(), t('Disable'));
diff --git a/core/modules/filter/src/Tests/FilterFormTest.php b/core/modules/filter/src/Tests/FilterFormTest.php
index 676e193..ca8d361 100644
--- a/core/modules/filter/src/Tests/FilterFormTest.php
+++ b/core/modules/filter/src/Tests/FilterFormTest.php
@@ -8,14 +8,14 @@
 namespace Drupal\filter\Tests;
 
 use Drupal\Component\Utility\String;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests form elements with associated text formats.
  *
  * @group filter
  */
-class FilterFormTest extends WebTestBase {
+class FilterFormTest extends BrowserTestBase {
 
   /**
    * Modules to enable for this test.
@@ -198,13 +198,13 @@ protected function assertNoSelect($id) {
   protected function assertOptions($id, array $expected_options, $selected) {
     $select = $this->xpath('//select[@id=:id]', array(':id' => $id));
     $select = reset($select);
-    $passed = $this->assertTrue($select instanceof \SimpleXMLElement, String::format('Field @id exists.', array(
+    $passed = $this->assertTrue($select->getAttribute('id') == $id, String::format('Field @id exists.', array(
       '@id' => $id,
     )));
 
     $found_options = $this->getAllOptions($select);
     foreach ($found_options as $found_key => $found_option) {
-      $expected_key = array_search($found_option->attributes()->value, $expected_options);
+      $expected_key = array_search($found_option->getAttribute('value'), $expected_options);
       if ($expected_key !== FALSE) {
         $this->pass(String::format('Option @option for field @id exists.', array(
           '@option' => $expected_options[$expected_key],
@@ -226,7 +226,7 @@ protected function assertOptions($id, array $expected_options, $selected) {
     }
     foreach ($found_options as $found_option) {
       $this->fail(String::format('Option @option for field @id does not exist.', array(
-        '@option' => $found_option->attributes()->value,
+        '@option' => $found_option->getAttribute('value'),
         '@id' => $id,
       )));
       $passed = FALSE;
@@ -252,7 +252,7 @@ protected function assertRequiredSelectAndOptions($id, array $options) {
       ':id' => $id,
     ));
     $select = reset($select);
-    $passed = $this->assertTrue($select instanceof \SimpleXMLElement, String::format('Required field @id exists.', array(
+    $passed = $this->assertTrue($select->getAttribute('id') == $id, String::format('Required field @id exists.', array(
       '@id' => $id,
     )));
     // A required select element has a "- Select -" option whose key is an empty
@@ -275,7 +275,7 @@ protected function assertEnabledTextarea($id) {
       ':id' => $id,
     ));
     $textarea = reset($textarea);
-    return $this->assertTrue($textarea instanceof \SimpleXMLElement, String::format('Enabled field @id exists.', array(
+    $this->assertTrue($textarea->getAttribute('id') == $id, String::format('Enabled field @id exists.', array(
       '@id' => $id,
     )));
   }
@@ -294,7 +294,7 @@ protected function assertDisabledTextarea($id) {
       ':id' => $id,
     ));
     $textarea = reset($textarea);
-    $passed = $this->assertTrue($textarea instanceof \SimpleXMLElement, String::format('Disabled field @id exists.', array(
+    $passed = $this->assertTrue($textarea->getAttribute('id') == $id, String::format('Disabled field @id exists.', array(
       '@id' => $id,
     )));
     $expected = 'This field has been disabled because you do not have sufficient permissions to edit it.';
diff --git a/core/modules/filter/src/Tests/FilterFormatAccessTest.php b/core/modules/filter/src/Tests/FilterFormatAccessTest.php
index 9f4234f..cedce2d 100644
--- a/core/modules/filter/src/Tests/FilterFormatAccessTest.php
+++ b/core/modules/filter/src/Tests/FilterFormatAccessTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\filter\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests access to text formats.
  *
  * @group filter
  */
-class FilterFormatAccessTest extends WebTestBase {
+class FilterFormatAccessTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -145,7 +145,7 @@ function testFormatPermissions() {
     ));
     $options = array();
     foreach ($elements as $element) {
-      $options[(string) $element['value']] = $element;
+      $options[$element->getValue()] = $element;
     }
     $this->assertTrue(isset($options[$this->allowed_format->format]), 'The allowed text format appears as an option when adding a new node.');
     $this->assertFalse(isset($options[$this->disallowed_format->format]), 'The disallowed text format does not appear as an option when adding a new node.');
diff --git a/core/modules/filter/src/Tests/FilterHtmlImageSecureTest.php b/core/modules/filter/src/Tests/FilterHtmlImageSecureTest.php
index 9876f51..db1a51e 100644
--- a/core/modules/filter/src/Tests/FilterHtmlImageSecureTest.php
+++ b/core/modules/filter/src/Tests/FilterHtmlImageSecureTest.php
@@ -8,14 +8,14 @@
 namespace Drupal\filter\Tests;
 
 use Drupal\Core\StreamWrapper\PublicStream;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests restriction of IMG tags in HTML input.
  *
  * @group filter
  */
-class FilterHtmlImageSecureTest extends WebTestBase {
+class FilterHtmlImageSecureTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -135,14 +135,14 @@ function testImageSource() {
       foreach ($this->xpath('//img[@testattribute="' . hash('sha256', $image) . '"]') as $element) {
         $found = TRUE;
         if ($converted == $red_x_image) {
-          $this->assertEqual((string) $element['src'], $red_x_image);
-          $this->assertEqual((string) $element['alt'], $alt_text);
-          $this->assertEqual((string) $element['title'], $title_text);
-          $this->assertEqual((string) $element['height'], '16');
-          $this->assertEqual((string) $element['width'], '16');
+          $this->assertEqual($element->getAttribute('src'), $red_x_image);
+          $this->assertEqual($element->getAttribute('alt'), $alt_text);
+          $this->assertEqual($element->getAttribute('title'), $title_text);
+          $this->assertEqual($element->getAttribute('height'), '16');
+          $this->assertEqual($element->getAttribute('width'), '16');
         }
         else {
-          $this->assertEqual((string) $element['src'], $converted);
+          $this->assertEqual($element->getAttribute('src'), $converted);
         }
       }
       $this->assertTrue($found, format_string('@image was found.', array('@image' => $image)));
diff --git a/core/modules/forum/src/Tests/ForumTest.php b/core/modules/forum/src/Tests/ForumTest.php
index e1d6749..97c0a64 100644
--- a/core/modules/forum/src/Tests/ForumTest.php
+++ b/core/modules/forum/src/Tests/ForumTest.php
@@ -155,7 +155,7 @@ function testForum() {
     $forum_arg = array(':forum' => 'forum-list-' . $this->forum['tid']);
 
     // Topics cell contains number of topics and number of unread topics.
-    $xpath = $this->buildXPathQuery('//tr[@id=:forum]//td[@class="topics"]', $forum_arg);
+    $xpath = $this->buildXPathQuery('//tr[@id=:forum]//td[@class="topics"]/text()[1]', $forum_arg);
     $topics = $this->xpath($xpath);
     $topics = trim($topics[0]);
     $this->assertEqual($topics, '6', 'Number of topics found.');
@@ -163,7 +163,7 @@ function testForum() {
     // Verify the number of unread topics.
     $unread_topics = $this->container->get('forum_manager')->unreadTopics($this->forum['tid'], $this->edit_any_topics_user->id());
     $unread_topics = format_plural($unread_topics, '1 new post', '@count new posts');
-    $xpath = $this->buildXPathQuery('//tr[@id=:forum]//td[@class="topics"]//a', $forum_arg);
+    $xpath = $this->buildXPathQuery('//tr[@id=:forum]//td[@class="topics"]//a/text()[1]', $forum_arg);
     $this->assertFieldByXPath($xpath, $unread_topics, 'Number of unread topics found.');
     // Verify that the forum name is in the unread topics text.
     $xpath = $this->buildXPathQuery('//tr[@id=:forum]//em[@class="placeholder"]', $forum_arg);
diff --git a/core/modules/history/src/Tests/HistoryTest.php b/core/modules/history/src/Tests/HistoryTest.php
index 9010511..238b8e6 100644
--- a/core/modules/history/src/Tests/HistoryTest.php
+++ b/core/modules/history/src/Tests/HistoryTest.php
@@ -27,7 +27,7 @@ class HistoryTest extends WebTestBase {
   /**
    * The main user for testing.
    *
-   * @var objec
+   * @var object
    */
   protected $user;
 
diff --git a/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php
index d111000..5cdf310 100644
--- a/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php
+++ b/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php
@@ -12,7 +12,7 @@
 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
 use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUser;
 use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -46,7 +46,7 @@
  *
  * @group language
  */
-class LanguageUILanguageNegotiationTest extends WebTestBase {
+class LanguageUILanguageNegotiationTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -122,7 +122,7 @@ function testUILanguageNegotiation() {
     );
     $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
     $textarea = current($this->xpath('//textarea'));
-    $lid = (string) $textarea[0]['name'];
+    $lid = $textarea->getAttribute('name');
     $edit = array(
       $lid => $language_browser_fallback_string,
     );
@@ -134,7 +134,7 @@ function testUILanguageNegotiation() {
     );
     $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
     $textarea = current($this->xpath('//textarea'));
-    $lid = (string) $textarea[0]['name'];
+    $lid = $textarea->getAttribute('name');
     $edit = array(
       $lid => $language_string,
     );
diff --git a/core/modules/locale/src/Tests/LocaleTranslationUiTest.php b/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
index 9e65440..2ec7733 100644
--- a/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
+++ b/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\locale\Tests;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Component\Utility\String;
@@ -18,7 +18,7 @@
  *
  * @group locale
  */
-class LocaleTranslationUiTest extends WebTestBase {
+class LocaleTranslationUiTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -496,7 +496,7 @@ function testUICustomizedStrings(){
 
     // Submit the translations without changing the translation.
     $textarea = current($this->xpath('//textarea'));
-    $lid = (string) $textarea[0]['name'];
+    $lid = $textarea->getAttribute('name');
     $edit = array(
       $lid => $translation->getString(),
     );
@@ -514,7 +514,7 @@ function testUICustomizedStrings(){
 
     // Submit the translations with a new translation.
     $textarea = current($this->xpath('//textarea'));
-    $lid = (string) $textarea[0]['name'];
+    $lid = $textarea->getAttribute('name');
     $edit = array(
       $lid => $this->randomName(100),
     );
diff --git a/core/modules/rest/src/Tests/CreateTest.php b/core/modules/rest/src/Tests/CreateTest.php
index 5476dbf..1417026 100644
--- a/core/modules/rest/src/Tests/CreateTest.php
+++ b/core/modules/rest/src/Tests/CreateTest.php
@@ -48,11 +48,11 @@ public function testCreate() {
 
       // Get the new entity ID from the location header and try to read it from
       // the database.
-      $location_url = $this->drupalGetHeader('location');
+      $location_url = $this->drupalGetHeader('Location');
       $url_parts = explode('/', $location_url);
       $id = end($url_parts);
       $loaded_entity = entity_load($entity_type, $id);
-      $this->assertNotIdentical(FALSE, $loaded_entity, 'The new ' . $entity_type . ' was found in the database.');
+      $this->assertNotIdentical(NULL, $loaded_entity, 'The new ' . $entity_type . ' was found in the database.');
       $this->assertEqual($entity->uuid(), $loaded_entity->uuid(), 'UUID of created entity is correct.');
       // @todo Remove the user reference field for now until deserialization for
       // entity references is implemented.
diff --git a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
index fa2e954..bac2ef6 100644
--- a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
+++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
@@ -94,7 +94,7 @@ public function testSerializerResponses() {
     $this->assertIdentical($actual_json, drupal_render($output), 'The expected JSON preview output was found.');
 
     // Test a 403 callback.
-    $this->drupalGet('test/serialize/denied');
+    $this->drupalGet('test/serialize/denied', array(), array('Accept: application/json'));
     $this->assertResponse(403);
 
     // Test the entity rows.
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php
similarity index 75%
copy from core/modules/simpletest/src/WebTestBase.php
copy to core/modules/simpletest/src/BrowserTestBase.php
index ac2f0a0..83626c7 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/BrowserTestBase.php
@@ -2,18 +2,17 @@
 
 /**
  * @file
- * Definition of \Drupal\simpletest\WebTestBase.
+ * Definition of \Drupal\simpletest\BrowserTestBase.
  */
 
 namespace Drupal\simpletest;
 
+use Behat\Mink\Element\NodeElement;
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Crypt;
-use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\String;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Database\Database;
-use Drupal\Core\Database\ConnectionNotDefinedException;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -21,16 +20,27 @@
 use Drupal\Core\Session\UserSession;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PublicStream;
-use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\block\Entity\Block;
+use Behat\Mink\Driver\Goutte\Client;
+use GuzzleHttp\Event\AbstractTransferEvent;
+use GuzzleHttp\Event\BeforeEvent;
+use GuzzleHttp\Event\RequestEvents;
+use GuzzleHttp\Event\SubscriberInterface;
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Post\PostFile;
 use Symfony\Component\HttpFoundation\Request;
+use Behat\Mink\Mink;
+use Behat\Mink\Session;
+use Behat\Mink\Driver\GoutteDriver;
+use GuzzleHttp\Client as GuzzleClient;
+use Symfony\Component\CssSelector\CssSelector;
 
 /**
  * Test case for typical Drupal tests.
  *
  * @ingroup testing
  */
-abstract class WebTestBase extends TestBase {
+abstract class BrowserTestBase extends TestBase implements SubscriberInterface {
 
   use AssertContentTrait;
 
@@ -49,13 +59,6 @@
   protected $url;
 
   /**
-   * The handle of the current cURL connection.
-   *
-   * @var resource
-   */
-  protected $curlHandle;
-
-  /**
    * The headers of the page currently loaded in the internal browser.
    *
    * @var Array
@@ -73,6 +76,13 @@
   protected $dumpHeaders = FALSE;
 
   /**
+   * The response code for the last request.
+   *
+   * @var int
+   */
+  protected $responseCode = NULL;
+
+  /**
    * The current user logged in using the internal browser.
    *
    * @var bool
@@ -164,6 +174,11 @@
   protected $curlCookies = array();
 
   /**
+   * Cookies send back in headers.
+   */
+  protected $cookies = array();
+
+  /**
    * An array of custom translations suitable for drupal_rewrite_settings().
    *
    * @var array
@@ -171,19 +186,90 @@
   protected $customTranslations;
 
   /**
+   * Current mink object.
+   *
+   * @var \Behat\Mink\Mink
+   */
+  protected $mink;
+
+  /**
    * Constructor for \Drupal\simpletest\WebTestBase.
    */
-  function __construct($test_id = NULL) {
+  public function __construct($test_id = NULL) {
     parent::__construct($test_id);
     $this->skipClasses[__CLASS__] = TRUE;
   }
 
   /**
+   * Resets the mink sessions.
+   */
+  protected function resetMink() {
+    $this->getMink()->resetSessions();
+  }
+
+  /**
+   * Initializes Mink session.
+   */
+  protected function initMink() {
+    global $base_url;
+    $defaults = array(
+      'timeout' => 30,
+      'verify' => FALSE,
+      'headers' => array(
+        'User-Agent' => $this->databasePrefix,
+      ),
+    );
+    $guzzle = new GuzzleClient(array('base_url' => $base_url, 'defaults' => $defaults));
+    $guzzle->getEmitter()->attach($this);
+    $client = new Client();
+    $client->setClient($guzzle);
+    $client->setHeader('User-Agent', $this->databasePrefix);
+    $client->followRedirects(FALSE);
+
+    if (isset($this->httpauth_credentials)) {
+      list($username, $password) = explode(':', $this->httpauth_credentials);
+      $client->setAuth($username, $password, $this->httpauth_method);
+    }
+    $this->mink = new Mink(array(
+      'goutte' => new Session(new GoutteDriver($client)),
+    ));
+    $this->mink->setDefaultSessionName('goutte');
+    // Set the User agent.
+    $this->getMink()->getSession()->setRequestHeader('User-Agent', $this->databasePrefix);
+  }
+
+  /**
+   * Returns the current mink object.
+   *
+   * @return \Behat\Mink\Mink
+   *   Current mink session.
+   */
+  protected function getMink() {
+    if (!isset($this->mink)) {
+      $this->initMink();
+    }
+    return $this->mink;
+  }
+
+  /**
+   * Returns the named Mink session.
+   *
+   * @param string $name
+   *   (optional) Mink session name. Defaults to null.
+   *
+   * @return \Behat\Mink\Session
+   *   The active session.
+   */
+  protected function getSession($name = NULL) {
+    return $this->getMink()->getSession($name);
+  }
+
+  /**
    * Get a node from the database based on its title.
    *
-   * @param $title
+   * @param string $title
    *   A node title, usually generated by $this->randomName().
-   * @param $reset
+   * @param bool $reset
    *   (optional) Whether to reset the entity cache.
    *
    * @return \Drupal\node\NodeInterface
@@ -448,7 +534,7 @@ protected function assertNoBlockAppears(Block $block) {
    *   The result from the xpath query.
    */
   protected function findBlockInstance(Block $block) {
-    return $this->xpath('//div[@id = :id]', array(':id' => 'block-' . $block->id()));
+    return $this->getSession()->getPage()->find('css', '#block-' . $block->id());
   }
 
   /**
@@ -1176,6 +1262,8 @@ protected function tearDown() {
     // testing so test classes containing multiple tests are not polluted.
     $this->curlClose();
     $this->curlCookies = array();
+    $this->cookies = array();
+    $this->resetMink();
   }
 
   /**
@@ -1188,43 +1276,15 @@ protected function tearDown() {
    */
   protected function curlInitialize() {
     global $base_url;
+    $this->responseCode = NULL;
 
-    if (!isset($this->curlHandle)) {
-      $this->curlHandle = curl_init();
-
-      // Some versions/configurations of cURL break on a NULL cookie jar, so
-      // supply a real file.
-      if (empty($this->cookieFile)) {
-        $this->cookieFile = $this->public_files_directory . '/cookie.jar';
-      }
-
-      $curl_options = array(
-        CURLOPT_COOKIEJAR => $this->cookieFile,
-        CURLOPT_URL => $base_url,
-        CURLOPT_FOLLOWLOCATION => FALSE,
-        CURLOPT_RETURNTRANSFER => TRUE,
-        // Required to make the tests run on HTTPS.
-        CURLOPT_SSL_VERIFYPEER => FALSE,
-        // Required to make the tests run on HTTPS.
-        CURLOPT_SSL_VERIFYHOST => FALSE,
-        CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'),
-        CURLOPT_USERAGENT => $this->databasePrefix,
-      );
-      if (isset($this->httpauth_credentials)) {
-        $curl_options[CURLOPT_HTTPAUTH] = $this->httpauth_method;
-        $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials;
-      }
-      // curl_setopt_array() returns FALSE if any of the specified options
-      // cannot be set, and stops processing any further options.
-      $result = curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
-      if (!$result) {
-        throw new \UnexpectedValueException('One or more cURL options could not be set.');
-      }
+    if (!isset($this->mink)) {
+      $this->initMink();
     }
     // We set the user agent header on each request so as to use the current
     // time and a new uniqid.
     if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) {
-      curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0]));
+      $this->getMink()->getSession()->setRequestHeader('User-Agent', drupal_generate_test_ua($matches[0]));
     }
   }
 
@@ -1259,7 +1319,7 @@ protected function curlExec($curl_options, $redirect = FALSE) {
       }
     }
 
-    $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
+    $url = empty($curl_options[CURLOPT_URL]) ? $this->getSession()->getCurrentUrl() : $curl_options[CURLOPT_URL];
 
     if (!empty($curl_options[CURLOPT_POST])) {
       // This is a fix for the Curl library to prevent Expect: 100-continue
@@ -1312,7 +1372,17 @@ protected function curlExec($curl_options, $redirect = FALSE) {
       $curl_options[CURLOPT_COOKIE] .= implode('; ', $cookies) . ';';
     }
 
-    curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
+    // Set request headers
+    if (!empty($curl_options[CURLOPT_HTTPHEADER])) {
+      $http_headers = array();
+      foreach ($curl_options[CURLOPT_HTTPHEADER] as $header) {
+        $pos = strpos($header, ':');
+        $header_name = substr($header, 0, $pos);
+        $header_value = trim(substr($header, $pos + 1));
+        $http_headers[$header_name] = $header_value;
+        $this->getSession()->setRequestHeader($header_name, $header_value);
+      }
+    }
 
     if (!$redirect) {
       // Reset headers, the session ID and the redirect counter.
@@ -1321,9 +1391,21 @@ protected function curlExec($curl_options, $redirect = FALSE) {
       $this->redirect_count = 0;
     }
 
-    $content = curl_exec($this->curlHandle);
-    $status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
+    $this->getSession()->visit($url);
+
+    if (isset($http_headers)) {
+      foreach ($http_headers as $header_name => $header_value) {
+        $this->getSession()->setRequestHeader($header_name, NULL);
+      }
+    }
+
+    $content = $this->getSession()->getPage()->getContent();
+    $status = $this->getSession()->getStatusCode();
+    $this->responseCode = $status;
 
+    /**
+     * May not be needed. @todo larowlan confirm
+     *
     // cURL incorrectly handles URLs with fragments, so instead of
     // letting cURL handle redirects we take of them ourselves to
     // to prevent fragments being sent to the web server as part
@@ -1338,9 +1420,9 @@ protected function curlExec($curl_options, $redirect = FALSE) {
         return $this->curlExec($curl_options, TRUE);
       }
     }
+    */
 
-    $this->setRawContent($content);
-    $this->url = isset($original_url) ? $original_url : curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL);
+    $this->drupalSetContent($content, isset($original_url) ? $original_url : $this->getSession()->getCurrentUrl());
 
     $message_vars = array(
       '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'),
@@ -1356,52 +1438,49 @@ protected function curlExec($curl_options, $redirect = FALSE) {
   /**
    * Reads headers and registers errors received from the tested site.
    *
-   * @param $curlHandler
-   *   The cURL handler.
-   * @param $header
-   *   An header.
+   * @param \GuzzleHttp\Event\AbstractTransferEvent $event
+   *   Complete event for request
    *
    * @see _drupal_log_error().
    */
-  protected function curlHeaderCallback($curlHandler, $header) {
-    // Header fields can be extended over multiple lines by preceding each
-    // extra line with at least one SP or HT. They should be joined on receive.
-    // Details are in RFC2616 section 4.
-    if ($header[0] == ' ' || $header[0] == "\t") {
-      // Normalize whitespace between chucks.
-      $this->headers[] = array_pop($this->headers) . ' ' . trim($header);
-    }
-    else {
-      $this->headers[] = $header;
-    }
-
-    // Errors are being sent via X-Drupal-Assertion-* headers,
-    // generated by _drupal_log_error() in the exact form required
-    // by \Drupal\simpletest\WebTestBase::error().
-    if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) {
-      // Call \Drupal\simpletest\WebTestBase::error() with the parameters from
-      // the header.
-      call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1])));
-    }
-
-    // Save cookies.
-    if (preg_match('/^Set-Cookie: ([^=]+)=(.+)/', $header, $matches)) {
-      $name = $matches[1];
-      $parts = array_map('trim', explode(';', $matches[2]));
-      $value = array_shift($parts);
-      $this->cookies[$name] = array('value' => $value, 'secure' => in_array('secure', $parts));
-      if ($name == $this->session_name) {
-        if ($value != 'deleted') {
-          $this->session_id = $value;
+  public function curlHeaderCallback(AbstractTransferEvent $event) {
+    if ($response = $event->getResponse()) {
+      $headers = $response->getHeaders();
+      foreach ($headers as $name => $value) {
+        $this->headers[$name] = implode(";", $value);
+
+        // Errors are being sent via X-Drupal-Assertion-* headers,
+        // generated by _drupal_log_error() in the exact form required
+        // by \Drupal\simpletest\WebTestBase::error().
+        if (preg_match('/^X-Drupal-Assertion-[0-9]+$/', $name)) {
+          // Call \Drupal\simpletest\WebTestBase::error() with the parameters from
+          // the header.
+          foreach ($value as $v) {
+            call_user_func_array(array(&$this, 'error'), unserialize(urldecode($v)));
+          }
         }
-        else {
-          $this->session_id = NULL;
+
+        // Save cookies.
+        if ($name === 'Set-Cookie') {
+          foreach ($value as $cookie) {
+            if (preg_match('/^([^=]+)=(.+)/', $cookie, $matches)) {
+              $cookie_name = $matches[1];
+              $parts = array_map('trim', explode(';', $matches[2]));
+              $cookie_value = array_shift($parts);
+              $this->cookies[$cookie_name] = array('value' => $cookie_value, 'secure' => in_array('secure', $parts));
+              if ($cookie_name == $this->session_name) {
+                if ($cookie_value != 'deleted') {
+                  $this->session_id = $cookie_value;
+                }
+                else {
+                  $this->session_id = NULL;
+                }
+              }
+            }
+          }
         }
       }
     }
-
-    // This is required by cURL.
-    return strlen($header);
   }
 
   /**
@@ -1595,13 +1674,12 @@ protected function drupalGetAJAX($path, array $options = array(), array $headers
    *   POST data, so it must already be urlencoded and contain a leading "&"
    *   (e.g., "&extra_var1=hello+world&extra_var2=you%26me").
    */
-  protected function drupalPostForm($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
+  protected function drupalPostForm($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = array()) {
     $submit_matches = FALSE;
     $ajax = is_array($submit);
     if (isset($path)) {
       $this->drupalGet($path, $options);
     }
-
     if ($this->parse()) {
       $edit_save = $edit;
       // Let's iterate over all the forms.
@@ -1609,24 +1687,26 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
       if (!empty($form_html_id)) {
         $xpath .= "[@id='" . $form_html_id . "']";
       }
+      /** @var \Behat\Mink\Element\NodeElement[] $forms */
       $forms = $this->xpath($xpath);
       foreach ($forms as $form) {
         // We try to set the fields of this form as specified in $edit.
         $edit = $edit_save;
         $post = array();
         $upload = array();
-        $submit_matches = $this->handleForm($post, $edit, $upload, $ajax ? NULL : $submit, $form);
-        $action = isset($form['action']) ? $this->getAbsoluteUrl((string) $form['action']) : $this->getUrl();
+        $submit_element = $this->handleForm($post, $edit, $upload, $ajax ? NULL : $submit, $form);
+        $action = $form->getAttribute('action') ? $this->getAbsoluteUrl($form->getAttribute('action')) : $this->getUrl();
         if ($ajax) {
           $action = $this->getAbsoluteUrl(!empty($submit['path']) ? $submit['path'] : 'system/ajax');
           // Ajax callbacks verify the triggering element if necessary, so while
           // we may eventually want extra code that verifies it in the
           // handleForm() function, it's not currently a requirement.
-          $submit_matches = TRUE;
+          // @todo this needs to be an Element.
+          $submit_element = TRUE;
         }
         // We post only if we managed to handle every field in edit and the
         // submit button matches.
-        if (!$edit && ($submit_matches || !isset($submit))) {
+        if (!$edit && ($submit_element || !isset($submit))) {
           $post_array = $post;
           if ($upload) {
             foreach ($upload as $key => $file) {
@@ -1644,10 +1724,20 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
               }
             }
           }
+          if (!$ajax) {
+            $submit_element->press();
+            $out = $this->getSession()->getPage()->getContent();
+          }
           else {
-            $post = $this->serializePostValues($post) . $extra_post;
+            // Guzzle only supports strings for submitted values.
+            $post = $post + ($extra_post ?: array());
+            foreach ($post as $key => $value) {
+              if (is_bool($value)) {
+                $post[$key] = '1';
+              }
+            }
+            $out = $this->drupalPost($action, 'application/vnd.drupal-ajax', $post, $options, $upload);
           }
-          $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers));
           // Ensure that any changes to variables in the other thread are picked
           // up.
           $this->refreshVariables();
@@ -1658,6 +1748,10 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
             $out = $new;
           }
 
+          if ($session_id = $this->getSession()->getCookie($this->getSessionName())) {
+            $this->session_id = $session_id;
+          }
+
           $verbose = 'POST request to: ' . $path;
           $verbose .= '<hr />Ending URL: ' . $this->getUrl();
           if ($this->dumpHeaders) {
@@ -1667,6 +1761,7 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
           $verbose .= '<hr />' . $out;
 
           $this->verbose($verbose);
+          $this->drupalSetContent($out, $this->getUrl());
           return $out;
         }
       }
@@ -1747,7 +1842,7 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
         $xpath = '//form[@id="' . $form_html_id . '"]' . $xpath;
       }
       $element = $this->xpath($xpath);
-      $element_id = (string) $element[0]['id'];
+      $element_id = $element[0]->getAttribute('id');
       $ajax_settings = $drupal_settings['ajax'][$element_id];
     }
 
@@ -1760,14 +1855,12 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
     }
     $ajax_html_ids = array();
     foreach ($this->xpath('//*[@id]') as $element) {
-      $ajax_html_ids[] = (string) $element['id'];
+      $ajax_html_ids[] = $element->getAttribute('id');
     }
     if (!empty($ajax_html_ids)) {
       $extra_post['ajax_html_ids'] = implode(' ', $ajax_html_ids);
     }
     $extra_post += $this->getAjaxPageStatePostData();
-    // Now serialize all the $extra_post values, and prepend it with an '&'.
-    $extra_post = '&' . $this->serializePostValues($extra_post);
 
     // Unless a particular path is specified, use the one specified by the
     // Ajax settings, or else 'system/ajax'.
@@ -1816,7 +1909,7 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
    * @see ajax.js
    */
   protected function drupalProcessAjaxResponse($content, array $ajax_response, array $ajax_settings, array $drupal_settings) {
-
+    // @todo larowlan refactor around Mink
     // ajax.js applies some defaults to the settings object, so do the same
     // for what's used by this function.
     $ajax_settings += array(
@@ -1903,8 +1996,16 @@ protected function drupalProcessAjaxResponse($content, array $ajax_response, arr
       }
     }
     $content = $dom->saveHTML();
-    $this->setRawContent($content);
-    $this->setDrupalSettings($drupal_settings);
+    $this->drupalSetContent($content);
+    $this->drupalSetSettings($drupal_settings);
+    // Hack this into the mink session. When we use a real JavaScript driver we
+    // can remove this nastiness.
+    $reflection = new \ReflectionClass($this->getSession()->getDriver());
+    $method = $reflection->getMethod('getCrawler');
+    $method->setAccessible(TRUE);
+    $crawler = $method->invoke($this->getSession()->getDriver());
+    $crawler->clear();
+    $crawler->addContent($content, 'text/html');
   }
 
   /**
@@ -1923,24 +2024,56 @@ protected function drupalProcessAjaxResponse($content, array $ajax_response, arr
    * @param array $options
    *   (optional) Options to be forwarded to the url generator. The 'absolute'
    *   option will automatically be enabled.
+   * @param array $files
+   *   (optional) Array of files to upload.
    *
-   * @return
-   *   The content returned from the call to curl_exec().
+   * @return string
+   *   The content returned from the Guzzle call.
    *
    * @see WebTestBase::getAjaxPageStatePostData()
-   * @see WebTestBase::curlExec()
    * @see url()
    */
-  protected function drupalPost($path, $accept, array $post, $options = array()) {
-    return $this->curlExec(array(
-      CURLOPT_URL => url($path, $options + array('absolute' => TRUE)),
-      CURLOPT_POST => TRUE,
-      CURLOPT_POSTFIELDS => $this->serializePostValues($post),
-      CURLOPT_HTTPHEADER => array(
-        'Accept: ' . $accept,
-        'Content-Type: application/x-www-form-urlencoded',
-      ),
+  protected function drupalPost($path, $accept, array $post, $options = array(), $files = array()) {
+    /** @var \GuzzleHttp\Client $guzzle */
+    $guzzle = $this->getSession()->getDriver()->getClient()->getClient();
+    $cookies = [];
+    if ($session_id = $this->getSession()->getCookie($this->session_name)) {
+      $cookies[$this->session_name] = $session_id;
+    }
+    $request = $guzzle->createRequest('POST', url($path, $options + array('absolute' => TRUE)), array(
+      'body' => $post,
+      'cookies' => $cookies,
+      'allow_redirects' => FALSE,
+      'timeout' => 30
     ));
+    if ($accept) {
+      $request->addHeader('Accept', $accept);
+    }
+    $request->addHeader('Content-Type', 'application/x-www-form-urlencoded');
+    if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) {
+      $request->setHeader('User-Agent', drupal_generate_test_ua($matches[0]));
+    }
+    foreach ($files as $name => $file) {
+      $request->getBody()->addFile(new PostFile($name, fopen($file, 'r')));
+    }
+    try {
+      $response = $guzzle->send($request);
+      $this->drupalSetContent($response->getBody());
+      $this->responseCode = $response->getStatusCode();
+    }
+    catch (RequestException $e) {
+      $response = $e->getResponse();
+      if ($response == NULL) {
+        $this->fail($e->getMessage());
+      }
+      else {
+        $this->drupalSetContent($response->getBody());
+        $this->responseCode = $response->getStatusCode();
+      }
+    }
+    $this->verbose('POST request to: ' . $path .
+      '<hr />' . $this->content);
+    return $this->content;
   }
 
   /**
@@ -1950,16 +2083,17 @@ protected function drupalPost($path, $accept, array $post, $options = array()) {
    *   The Ajax page state POST data.
    */
   protected function getAjaxPageStatePostData() {
+    // @todo larowlan refactor around Mink
     $post = array();
     $drupal_settings = $this->drupalSettings;
     if (isset($drupal_settings['ajaxPageState'])) {
       $post['ajax_page_state[theme]'] = $drupal_settings['ajaxPageState']['theme'];
       $post['ajax_page_state[theme_token]'] = $drupal_settings['ajaxPageState']['theme_token'];
       foreach ($drupal_settings['ajaxPageState']['css'] as $key => $value) {
-        $post["ajax_page_state[css][$key]"] = 1;
+        $post["ajax_page_state[css][$key]"] = '1';
       }
       foreach ($drupal_settings['ajaxPageState']['js'] as $key => $value) {
-        $post["ajax_page_state[js][$key]"] = 1;
+        $post["ajax_page_state[js][$key]"] = '1';
       }
     }
     return $post;
@@ -2024,12 +2158,13 @@ protected function cronRun() {
    *   Either the new page content or FALSE.
    */
   protected function checkForMetaRefresh() {
-    if (strpos($this->getRawContent(), '<meta ') && $this->parse()) {
+    // @todo larowlan refactor around Mink
+    if (strpos($this->drupalGetContent(), '<meta ') && $this->parse()) {
       $refresh = $this->xpath('//meta[@http-equiv="Refresh"]');
       if (!empty($refresh)) {
         // Parse the content attribute of the meta tag for the format:
         // "[delay]: URL=[page_to_redirect_to]".
-        if (preg_match('/\d+;\s*URL=(?<url>.*)/i', $refresh[0]['content'], $match)) {
+        if (preg_match('/\d+;\s*URL=(?<url>.*)/i', $refresh[0]->getAttribute('content'), $match)) {
           return $this->drupalGet($this->getAbsoluteUrl(decode_entities($match['url'])));
         }
       }
@@ -2052,6 +2187,7 @@ protected function checkForMetaRefresh() {
    *   The retrieved headers, also available as $this->getRawContent()
    */
   protected function drupalHead($path, array $options = array(), array $headers = array()) {
+    // @todo larowlan refactor around Mink
     $options['absolute'] = TRUE;
     $url = $this->container->get('url_generator')->generateFromPath($path, $options);
     $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers));
@@ -2073,29 +2209,32 @@ protected function drupalHead($path, array $options = array(), array $headers =
    * Ensure that the specified fields exist and attempt to create POST data in
    * the correct manner for the particular field type.
    *
-   * @param $post
+   * @param array $post
    *   Reference to array of post values.
-   * @param $edit
+   * @param array $edit
    *   Reference to array of edit values to be checked against the form.
-   * @param $submit
+   * @param array $upload
+   *   Array of file uploads
+   * @param string $submit
    *   Form submit button value.
-   * @param $form
+   * @param \Behat\Mink\Element\NodeElement|\Drupal\simpletest\MinkNodeElementDecorator $form
    *   Array of form elements.
    *
-   * @return
-   *   Submit value matches a valid submit input in the form.
+   * @return \Behat\Mink\Element\NodeElement|\Drupal\simpletest\MinkNodeElementDecorator|bool
+   *   The submit button for the form to trigger the POST.
    */
   protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
     // Retrieve the form elements.
-    $elements = $form->xpath('.//input[not(@disabled)]|.//textarea[not(@disabled)]|.//select[not(@disabled)]');
+    /** @var \Behat\Mink\Element\NodeElement[] $elements */
+    $elements = $form->findAll('xpath', './/input[not(@disabled)]|.//textarea[not(@disabled)]|.//select[not(@disabled)]');
     $submit_matches = FALSE;
     foreach ($elements as $element) {
       // SimpleXML objects need string casting all the time.
-      $name = (string) $element['name'];
+      $name = $element->getAttribute('name');
       // This can either be the type of <input> or the name of the tag itself
       // for <select> or <textarea>.
-      $type = isset($element['type']) ? (string) $element['type'] : $element->getName();
-      $value = isset($element['value']) ? (string) $element['value'] : '';
+      $type = $element->getAttribute('type') ? $element->getAttribute('type') : $element->getTagName();
+      $value = $element->getValue();
       $done = FALSE;
       if (isset($edit[$name])) {
         switch ($type) {
@@ -2114,12 +2253,23 @@ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
           case 'time':
           case 'datetime':
           case 'datetime-local';
-            $post[$name] = $edit[$name];
+            $element->setValue($edit[$name]);
+            unset($edit[$name]);
+            break;
+          case 'select':
+            if (!$element->getAttribute('multiple') && is_array($edit[$name])) {
+              // Handle faulty edit values that pass an array to a single value
+              // select field.
+              $element->setValue(reset($edit[$name]));
+            }
+            else {
+              $element->setValue($edit[$name]);
+            }
             unset($edit[$name]);
             break;
           case 'radio':
-            if ($edit[$name] == $value) {
-              $post[$name] = $edit[$name];
+            if ($element->getAttribute('name') == $name && $element->getAttribute('value') == $edit[$name]) {
+              $element->setValue($edit[$name]);
               unset($edit[$name]);
             }
             break;
@@ -2128,51 +2278,17 @@ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
             // otherwise the checkbox will be set to its value regardless
             // of $edit.
             if ($edit[$name] === FALSE) {
+              $element->uncheck();
               unset($edit[$name]);
               continue 2;
             }
             else {
               unset($edit[$name]);
-              $post[$name] = $value;
-            }
-            break;
-          case 'select':
-            $new_value = $edit[$name];
-            $options = $this->getAllOptions($element);
-            if (is_array($new_value)) {
-              // Multiple select box.
-              if (!empty($new_value)) {
-                $index = 0;
-                $key = preg_replace('/\[\]$/', '', $name);
-                foreach ($options as $option) {
-                  $option_value = (string) $option['value'];
-                  if (in_array($option_value, $new_value)) {
-                    $post[$key . '[' . $index++ . ']'] = $option_value;
-                    $done = TRUE;
-                    unset($edit[$name]);
-                  }
-                }
-              }
-              else {
-                // No options selected: do not include any POST data for the
-                // element.
-                $done = TRUE;
-                unset($edit[$name]);
-              }
-            }
-            else {
-              // Single select box.
-              foreach ($options as $option) {
-                if ($new_value == $option['value']) {
-                  $post[$name] = $new_value;
-                  unset($edit[$name]);
-                  $done = TRUE;
-                  break;
-                }
-              }
+              $element->check();
             }
             break;
           case 'file':
+            $element->attachFile($edit[$name]);
             $upload[$name] = $edit[$name];
             unset($edit[$name]);
             break;
@@ -2181,27 +2297,11 @@ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
       if (!isset($post[$name]) && !$done) {
         switch ($type) {
           case 'textarea':
-            $post[$name] = (string) $element;
+            $post[$name] = $element->getValue();
             break;
           case 'select':
-            $single = empty($element['multiple']);
-            $first = TRUE;
-            $index = 0;
-            $key = preg_replace('/\[\]$/', '', $name);
-            $options = $this->getAllOptions($element);
-            foreach ($options as $option) {
-              // For single select, we load the first option, if there is a
-              // selected option that will overwrite it later.
-              if ($option['selected'] || ($first && $single)) {
-                $first = FALSE;
-                if ($single) {
-                  $post[$name] = (string) $option['value'];
-                }
-                else {
-                  $post[$key . '[' . $index++ . ']'] = (string) $option['value'];
-                }
-              }
-            }
+            $single = !$element->getAttribute('multiple');
+            $post[$name] = $element->getValue();
             break;
           case 'file':
             break;
@@ -2209,12 +2309,12 @@ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
           case 'image':
             if (isset($submit) && $submit == $value) {
               $post[$name] = $value;
-              $submit_matches = TRUE;
+              $submit_matches = $element;
             }
             break;
           case 'radio':
           case 'checkbox':
-            if (!isset($element['checked'])) {
+            if (!$element->isChecked()) {
               break;
             }
             // Deliberate no break.
@@ -2229,6 +2329,70 @@ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
   }
 
   /**
+   * Performs an xpath search on the contents of the internal browser.
+   *
+   * The search is relative to the root element (HTML tag normally) of the page.
+   *
+   * @param string $xpath
+   *   The xpath string to use in the search.
+   * @param array $arguments
+   *   An array of arguments with keys in the form ':name' matching the
+   *   placeholders in the query. The values may be either strings or numeric
+   *   values.
+   *
+   * @return \Behat\Mink\Element\NodeElement[]
+   *   The return value of the xpath search. For details on the xpath string
+   *   format and return values see the SimpleXML documentation,
+   *   http://php.net/manual/function.simplexml-element-xpath.php.
+   */
+  protected function xpath($xpath, array $arguments = array()) {
+    $xpath = $this->buildXPathQuery($xpath, $arguments);
+    $elements = $this->getSession()->getPage()->findAll('xpath', $xpath);
+    $decorated = array();
+    foreach ($elements as $element) {
+      $decorated[] = MinkNodeElementDecorator::decorate($element);
+    }
+    return $decorated;
+  }
+
+  /**
+   * Performs a CSS selection based search on the contents of the internal
+   * browser. The search is relative to the root element (HTML tag normally) of
+   * the page.
+   *
+   * @param $selector string
+   *   CSS selector to use in the search.
+   *
+   * @return \SimpleXMLElement
+   *   The return value of the xpath search performed after converting the css
+   *   selector to an XPath selector.
+   */
+  protected function cssSelect($selector) {
+    return $this->xpath(CssSelector::toXPath($selector));
+  }
+
+  /**
+   * Get all option elements, including nested options, in a select.
+   *
+   * @param $element
+   *   The element for which to get the options.
+   *
+   * @return NodeElement[]
+   *   Option elements in select.
+   */
+  protected function getAllOptions($element) {
+    $options = $element->findAll('css', 'option');
+    $decorated = array();
+    foreach ($options as $option) {
+      $decorated[] = MinkNodeElementDecorator::decorate($option);
+    }
+    if (count($decorated) === 1) {
+      return $decorated[0];
+    }
+    return MinkNodeElementCollectionDecorator::decorate($decorated);
+  }
+
+  /**
    * Follows a link by name.
    *
    * Will click the first link found with this link text by default, or a later
@@ -2247,10 +2411,15 @@ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
    */
   protected function clickLink($label, $index = 0) {
     $url_before = $this->getUrl();
+    /** @var \Behat\Mink\Element\NodeElement[] $urls */
     $urls = $this->xpath('//a[normalize-space()=:label]', array(':label' => $label));
     if (isset($urls[$index])) {
-      $url_target = $this->getAbsoluteUrl($urls[$index]['href']);
-      $this->pass(String::format('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), 'Browser');
+      $url_target = $this->getAbsoluteUrl($urls[$index]->getAttribute('href'));
+    }
+
+    $this->assertTrue(isset($urls[$index]), String::format('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), 'Browser');
+
+    if (isset($url_target)) {
       return $this->drupalGet($url_target);
     }
     $this->fail(String::format('Link $label does not exist on @url_before', array('%label' => $label, '@url_before' => $url_before)), 'Browser');
@@ -2295,7 +2464,7 @@ protected function getAbsoluteUrl($path) {
    *   The current URL.
    */
   protected function getUrl() {
-    return $this->url;
+    return $this->getSession()->getCurrentUrl();
   }
 
   /**
@@ -2320,32 +2489,8 @@ protected function getUrl() {
    *   Values for duplicate headers are stored as a single comma-separated list.
    */
   protected function drupalGetHeaders($all_requests = FALSE) {
-    $request = 0;
-    $headers = array($request => array());
-    foreach ($this->headers as $header) {
-      $header = trim($header);
-      if ($header === '') {
-        $request++;
-      }
-      else {
-        if (strpos($header, 'HTTP/') === 0) {
-          $name = ':status';
-          $value = $header;
-        }
-        else {
-          list($name, $value) = explode(':', $header, 2);
-          $name = strtolower($name);
-        }
-        if (isset($headers[$request][$name])) {
-          $headers[$request][$name] .= ',' . trim($value);
-        }
-        else {
-          $headers[$request][$name] = trim($value);
-        }
-      }
-    }
-    if (!$all_requests) {
-      $headers = array_pop($headers);
+    if (!($headers = $this->headers)) {
+      $headers = $this->getSession()->getResponseHeaders();
     }
     return $headers;
   }
@@ -2369,23 +2514,16 @@ protected function drupalGetHeaders($all_requests = FALSE) {
    *   The HTTP header value or FALSE if not found.
    */
   protected function drupalGetHeader($name, $all_requests = FALSE) {
-    $name = strtolower($name);
-    $header = FALSE;
-    if ($all_requests) {
-      foreach (array_reverse($this->drupalGetHeaders(TRUE)) as $headers) {
-        if (isset($headers[$name])) {
-          $header = $headers[$name];
-          break;
-        }
+    $headers = $this->drupalGetHeaders();
+    if (isset($headers[$name])) {
+      if (is_array($headers[$name])) {
+        return reset($headers[$name]);
       }
-    }
-    else {
-      $headers = $this->drupalGetHeaders();
-      if (isset($headers[$name])) {
-        $header = $headers[$name];
+      else {
+        return $headers[$name];
       }
     }
-    return $header;
+    return FALSE;
   }
 
   /**
@@ -2395,7 +2533,9 @@ protected function drupalGetHeader($name, $all_requests = FALSE) {
    *   Use getRawContent().
    */
   protected function drupalGetContent() {
-    return $this->getRawContent();
+    // @todo Refactor to use $this->getSession()->getPage()->getContent() once
+    //   MinkSession has been sub-classed to allow setting content.
+    return $this->content;
   }
 
   /**
@@ -2440,7 +2580,9 @@ protected function drupalGetMails($filter = array()) {
    * @deprecated 8.x
    *   Use setRawContent().
    */
-  protected function drupalSetContent($content) {
+  protected function drupalSetContent($content, $url = 'internal:') {
+    // @todo Subclass MinkSession to include the ability to setPage() for
+    //   internal paths.
     $this->setRawContent($content);
   }
 
@@ -2489,6 +2631,345 @@ protected function assertUrl($path, array $options = array(), $message = '', $gr
   }
 
   /**
+   * Pass if the page title is the given string.
+   *
+   * @param $title
+   *   The string the title should be.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertTitle($title, $message = '', $group = 'Other') {
+    $element = current($this->xpath('//title'));
+    $actual = $element->getText();
+    if (!$message) {
+      $message = String::format('Page title @actual is equal to @expected.', array(
+        '@actual' => var_export($actual, TRUE),
+        '@expected' => var_export($title, TRUE),
+      ));
+    }
+    return $this->assertEqual($actual, $title, $message, $group);
+  }
+
+  /**
+   * Pass if the page title is not the given string.
+   *
+   * @param $title
+   *   The string the title should not be.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoTitle($title, $message = '', $group = 'Other') {
+    $element = current($this->xpath('//title'));
+    $actual = $element->getText();
+    if (!$message) {
+      $message = String::format('Page title @actual is not equal to @unexpected.', array(
+        '@actual' => var_export($actual, TRUE),
+        '@unexpected' => var_export($title, TRUE),
+      ));
+    }
+    return $this->assertNotEqual($actual, $title, $message, $group);
+  }
+
+  /**
+   * Asserts that a field exists in the current page by the given XPath.
+   *
+   * @param $xpath
+   *   XPath used to find the field.
+   * @param $value
+   *   (optional) Value of the field to assert. You may pass in NULL (default)
+   *   to skip checking the actual value, while still checking that the field
+   *   exists.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
+    $xpath = $this->buildXPathQuery($xpath);
+    $elements = $this->getSession()->getPage()->findAll('xpath', $xpath);
+    if (!isset($value)) {
+      return $this->assert(!empty($elements), $message, $group);
+    }
+    if (empty($elements)) {
+      return $this->assert(FALSE, $message, $group);
+    }
+
+    /** @var $element NodeElement */
+    foreach ($elements as $element) {
+      if ($element->hasAttribute('value') && $element->getAttribute('value') == $value) {
+        return $this->assert(TRUE, $message, $group);
+      }
+      elseif ($element->getTagName() === 'select') {
+        $selected_option = $element->find('xpath', '//option[@selected="selected"]');
+        if (empty($selected_option)) {
+          $first_option = $element->find('xpath', '//option');
+          if (!empty($first_option) && $first_option->getAttribute('value') == $value) {
+            return $this->assert(TRUE, $message, $group);
+          }
+        }
+        elseif ($selected_option->getAttribute('value') == $value) {
+          return $this->assert(TRUE, $message, $group);
+        }
+      }
+      elseif ($element->getText() == $value) {
+        return $this->assert(TRUE, $message, $group);
+      }
+    }
+    return $this->assert(FALSE, $message, $group);
+  }
+
+  /**
+   * Get the selected value from a select field.
+   *
+   * @param NodeElement $element
+   *   The select element.
+   *
+   * @return
+   *   The selected value or FALSE.
+   */
+  protected function getSelectedItem($element) {
+    $selected_option = $element->find('xpath', '//option[@selected="selected"]');
+    if ($selected_option) {
+      return $selected_option->getAttribute('value');
+    }
+    return FALSE;
+  }
+
+  /**
+   * Asserts that a field doesn't exist or its value doesn't match, by XPath.
+   *
+   * @param $xpath
+   *   XPath used to find the field.
+   * @param $value
+   *   (optional) Value for the field, to assert that the field's value on the
+   *   page doesn't match it.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
+    $xpath = $this->buildXPathQuery($xpath);
+    $elements = $this->getSession()->getPage()->findAll('xpath', $xpath);
+    if (!isset($value)) {
+      return $this->assert(empty($elements), $message, $group);
+    }
+    if (empty($elements)) {
+      return $this->assert(TRUE, $message, $group);
+    }
+
+    /** @var $element NodeElement */
+    foreach ($elements as $element) {
+      if ($element->hasAttribute('value') && $element->getAttribute('value') == $value) {
+        return $this->assert(FALSE, $message, $group);
+      }
+      elseif ($element->getTagName() === 'select') {
+        $selected_option = $element->find('xpath', '//option[@selected="selected"]');
+        if (empty($selected_option)) {
+          $first_option = $element->find('xpath', '//option');
+          if (!empty($first_option) && $first_option->getAttribute('value') == $value) {
+            return $this->assert(FALSE, $message, $group);
+          }
+        }
+        elseif ($selected_option->getAttribute('value') == $value) {
+          return $this->assert(FALSE, $message, $group);
+        }
+      }
+      elseif ($element->getText() == $value) {
+        return $this->assert(FALSE, $message, $group);
+      }
+    }
+    return $this->assert(TRUE, $message, $group);
+  }
+
+  /**
+   * Asserts that a checkbox field in the current page is checked.
+   *
+   * @param $id
+   *   ID of field to assert.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Browser'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertFieldChecked($id, $message = '', $group = 'Browser') {
+    $elements = $this->xpath('//input[@id=:id]', array(':id' => $id));
+    if ($elements[0]->getAttribute('type') == 'radio') {
+      return $this->assertTrue(isset($elements[0]) && $elements[0]->getValue() == $elements[0]->getAttribute('value'), $message ? $message : String::format('Checkbox field @id is checked.', array('@id' => $id)), $group);
+    }
+    else {
+      return $this->assertTrue(isset($elements[0]) && $elements[0]->getAttribute('checked') == 'checked', $message ? $message : String::format('Checkbox field @id is checked.', array('@id' => $id)), $group);
+    }
+  }
+
+  /**
+   * Asserts that a checkbox field in the current page is not checked.
+   *
+   * @param $id
+   *   ID of field to assert.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Browser'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoFieldChecked($id, $message = '', $group = 'Browser') {
+    /** @var \Behat\Mink\Element\NodeElement[] $elements */
+    $elements = $this->xpath('//input[@id=:id]', array(':id' => $id));
+    if ($elements[0]->getAttribute('type') == 'radio') {
+      return $this->assertTrue(isset($elements[0]) && $elements[0]->getValue() != $elements[0]->getAttribute('value'), $message ? $message : String::format('Checkbox field @id is not checked.', array('@id' => $id)), $group);
+    }
+    else {
+      return $this->assertTrue(isset($elements[0]) && $elements[0]->getAttribute('checked') != 'checked', $message ? $message : String::format('Checkbox field @id is not checked.', array('@id' => $id)), $group);
+    }
+  }
+
+  /**
+   * Asserts that a select option in the current page is checked.
+   *
+   * @param $id
+   *   ID of select field to assert.
+   * @param $option
+   *   Option to assert.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Browser'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   *
+   * @todo $id is unusable. Replace with $name.
+   */
+  protected function assertOptionSelected($id, $option, $message = '', $group = 'Browser') {
+    if ($option) {
+      $elements = $this->getSession()->getPage()->find('css', "#$id option[value=$option]");
+      return $this->assertTrue($elements && $elements->getAttribute('selected') == 'selected', $message ? $message : String::format('Option @option for field @id is selected.', array('@option' => $option, '@id' => $id)), $group);
+    }
+    else {
+      $elements = $this->getSession()->getPage()->find('css', "#$id option[selected=selected]");
+      return $this->assertTrue($elements && $option == $elements->getAttribute('value'), $message ? $message : String::format('Option @option for field @id is selected.', array('@option' => $option, '@id' => $id)), $group);
+    }
+  }
+
+  /**
+   * Asserts that a select option in the current page is not checked.
+   *
+   * @param $id
+   *   ID of select field to assert.
+   * @param $option
+   *   Option to assert.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Browser'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoOptionSelected($id, $option, $message = '', $group = 'Browser') {
+    $elements = $this->getSession()->getPage()->find('css', "#$id option[value=$option]");
+    return $this->assertTrue($elements && $elements->getAttribute('selected') != 'selected', $message ? $message : String::format('Option @option for field @id is not selected.', array('@option' => $option, '@id' => $id)), $group);
+  }
+
+  /**
+   * Asserts that each HTML ID is used for just a single element.
+   *
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   * @param $ids_to_skip
+   *   An optional array of ids to skip when checking for duplicates. It is
+   *   always a bug to have duplicate HTML IDs, so this parameter is to enable
+   *   incremental fixing of core code. Whenever a test passes this parameter,
+   *   it should add a "todo" comment above the call to this function explaining
+   *   the legacy bug that the test wishes to ignore and including a link to an
+   *   issue that is working to fix that legacy bug.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoDuplicateIds($message = '', $group = 'Other', $ids_to_skip = array()) {
+    $status = TRUE;
+    foreach ($this->xpath('//*[@id]') as $element) {
+      $id = $element->getAttribute('id');
+      if (isset($seen_ids[$id]) && !in_array($id, $ids_to_skip)) {
+        $this->fail(String::format('The HTML ID %id is unique.', array('%id' => $id)), $group);
+        $status = FALSE;
+      }
+      $seen_ids[$id] = TRUE;
+    }
+    return $this->assert($status, $message, $group);
+  }
+
+  /**
    * Asserts the page responds with the specified response code.
    *
    * @param $code
@@ -2508,7 +2989,9 @@ protected function assertUrl($path, array $options = array(), $message = '', $gr
    *   Assertion result.
    */
   protected function assertResponse($code, $message = '', $group = 'Browser') {
-    $curl_code = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
+    if (!($curl_code = $this->responseCode)) {
+      $curl_code = $this->getSession()->getStatusCode();
+    }
     $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code;
     return $this->assertTrue($match, $message ? $message : String::format('HTTP response expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), $group);
   }
@@ -2533,7 +3016,7 @@ protected function assertResponse($code, $message = '', $group = 'Browser') {
    *   Assertion result.
    */
   protected function assertNoResponse($code, $message = '', $group = 'Browser') {
-    $curl_code = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
+    $curl_code = $this->getSession()->getStatusCode();
     $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code;
     return $this->assertFalse($match, $message ? $message : String::format('HTTP response not expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), $group);
   }
@@ -2698,4 +3181,29 @@ protected function prepareRequestForGenerator($clean_urls = TRUE, $override_serv
     $generator->updateFromRequest();
     return $request;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEvents() {
+    return [
+      'before'   => ['onBefore', 100],
+      'complete' => ['curlHeaderCallback', RequestEvents::EARLY],
+      'error'    => ['curlHeaderCallback', RequestEvents::EARLY],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onBefore(BeforeEvent $event) {
+    $request = $event->getRequest();
+    // We set the user agent header on each request so as to use the current
+    // time and a new uniqid.
+    if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) {
+      // Ensure redirects get a new User-Agent.
+      $request->setHeader('User-Agent', drupal_generate_test_ua($matches[0]));
+    }
+  }
+
 }
diff --git a/core/modules/simpletest/src/MinkNodeElementCollectionDecorator.php b/core/modules/simpletest/src/MinkNodeElementCollectionDecorator.php
new file mode 100644
index 0000000..1bc3d93
--- /dev/null
+++ b/core/modules/simpletest/src/MinkNodeElementCollectionDecorator.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Provides BC decorator for  collection of \Behat\Mink\Element\NodeElements.
+ */
+
+namespace Drupal\simpletest;
+
+use Behat\Mink\Element\NodeElement;
+
+class MinkNodeElementCollectionDecorator implements \ArrayAccess, \Iterator, \Countable {
+
+  /**
+   * The decorated node element.
+   *
+   * @var \Behat\Mink\Element\NodeElement[]
+   */
+  protected $nodeElements = [];
+
+  /**
+   * The position of the iterator.
+   */
+  protected $position = 0;
+
+  /**
+   * Decorates an existing node element.
+   *
+   * @param array $node_elements
+   *
+   * @return \Drupal\simpletest\MinkNodeElementCollectionDecorator
+   *   The decorated element.
+   */
+  public static function decorate(array $node_elements) {
+    return new static($node_elements);
+  }
+
+  /**
+   * Constructs a new MinkNodeElementDecorator.
+   *
+   * @param array $node_elements
+   *   The node element to decorate.
+   */
+  public function __construct(array $node_elements) {
+    $this->nodeElements = $node_elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetExists($offset) {
+    return isset($this->nodeElements[$offset]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetGet($offset) {
+    return $this->nodeElements[$offset];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {
+    unset($this->nodeElements[$offset]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __call($method, $arguments) {
+    return call_user_func_array(array($this->nodeElements[0], $method), $arguments);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __get($name) {
+    return $this->nodeElements[0]->{$name};
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return $this->nodeElements[0]->getText();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function current() {
+    return $this->nodeElements[$this->position];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function next() {
+    $this->position++;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function key() {
+    return $this->position;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function valid() {
+    return isset($this->nodeElements[$this->position]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewind() {
+    $this->position = 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    return count($this->nodeElements);
+  }
+}
diff --git a/core/modules/simpletest/src/MinkNodeElementDecorator.php b/core/modules/simpletest/src/MinkNodeElementDecorator.php
new file mode 100644
index 0000000..bccb732
--- /dev/null
+++ b/core/modules/simpletest/src/MinkNodeElementDecorator.php
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * @file
+ * Contains a BC shim to decorate \Behat\Mink\Element\NodeElement.
+ */
+
+namespace Drupal\simpletest;
+
+use Behat\Mink\Element\NodeElement;
+
+class MinkNodeElementDecorator implements \ArrayAccess, \IteratorAggregate {
+
+  /**
+   * The decorated node element.
+   *
+   * @var \Behat\Mink\Element\NodeElement
+   */
+  protected $nodeElement;
+
+  /**
+   * Decorates an existing node element.
+   *
+   * @param \Behat\Mink\Element\NodeElement $node_element
+   *   The node element to decorate.
+   *
+   * @return \Drupal\simpletest\MinkNodeElementDecorator
+   *   The decorated element.
+   */
+  public static function decorate(NodeElement $node_element) {
+    return new static($node_element);
+  }
+
+  /**
+   * Constructs a new MinkNodeElementDecorator.
+   *
+   * @param \Behat\Mink\Element\NodeElement $node_element
+   *   The node element to decorate.
+   */
+  public function __construct(NodeElement $node_element) {
+    $this->nodeElement = $node_element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetExists($offset) {
+    if (is_int($offset)) {
+      // Attempt to access first child.
+      $children = $this->nodeElement->findAll('css', '*');
+      return isset($children[$offset]);
+    }
+    else {
+      // Property access.
+      return (bool) $this->nodeElement->getAttribute($offset);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetGet($offset) {
+    if (is_int($offset)) {
+      // Attempt to access first child.
+      $children = $this->nodeElement->findAll('css', '*');
+      if (isset($children[$offset])) {
+        return static::decorate($children[$offset]);
+      }
+      return NULL;
+    }
+    else {
+      // Property access.
+      return $this->nodeElement->getAttribute($offset);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __call($method, $arguments) {
+    // @see SimpleXMLElement::children()
+    if ($method == 'children') {
+      $child_nodes = $this->nodeElement->findAll('css', '*');
+      $children = [];
+      foreach ($child_nodes as $child_node) {
+        $children[] = static::decorate($child_node);
+      }
+      return MinkNodeElementCollectionDecorator::decorate($children);
+    }
+    // @see SimpleXMLElement::attributes()
+    if ($method == 'attributes') {
+      // This limits us to the Goutte driver here, but this whole class is a BC
+      // shim and so it will be removed in its entirety.
+      $xpath = $this->nodeElement->getXpath();
+      $reflection = new \ReflectionClass($this->nodeElement->getSession()->getDriver());
+      $method = $reflection->getMethod('getCrawler');
+      $method->setAccessible(TRUE);
+      $crawler = $method->invoke($this->nodeElement->getSession()->getDriver());
+      $element = $crawler->filterXPath($xpath)->eq(0)->getNode(0);
+      $attributes = array();
+      foreach ($element->attributes as $name => $attribute) {
+        $attributes[$name] = $attribute->value;
+      }
+      return ObjectArrayDecorator::decorate($attributes);
+    }
+    // @see SimpleXMLElement::getName()
+    if ($method == 'getName') {
+      $method = 'getTagName';
+    }
+    // @see SimpleXMLElement::asXML()
+    if ($method == 'asXML') {
+      $method = 'getHtml';
+    }
+    // @see SimpleXMLElement::xpath()
+    if ($method == 'xpath') {
+      $method = 'getXpath';
+    }
+    return call_user_func_array(array($this->nodeElement, $method), $arguments);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __get($name) {
+    $elements = $this->nodeElement->findAll('css', "*> " . $name);
+    $children = array();
+    foreach ($elements as $element) {
+      $children[] = static::decorate($element);
+    }
+    if (count($children) === 1) {
+      return $children[0];
+    }
+    return MinkNodeElementCollectionDecorator::decorate($children);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return $this->nodeElement->getText();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIterator() {
+    return new \ArrayIterator(array($this));
+  }
+}
diff --git a/core/modules/simpletest/src/ObjectArrayDecorator.php b/core/modules/simpletest/src/ObjectArrayDecorator.php
new file mode 100644
index 0000000..4484a06
--- /dev/null
+++ b/core/modules/simpletest/src/ObjectArrayDecorator.php
@@ -0,0 +1,113 @@
+<?php
+namespace Drupal\simpletest;
+
+/**
+ * Decorate an array to support object property access for the keys.
+ */
+class ObjectArrayDecorator implements \ArrayAccess, \Iterator, \Countable {
+  /**
+   * Array being decorated.
+   */
+  protected $data;
+
+  /**
+   * The position of the iterator.
+   */
+  protected $position = 0;
+
+  /**
+   * Default constructor.
+   */
+  private function __construct(array $data) {
+    $this->data = $data;
+  }
+
+  /**
+   * Decorates an array to support object property access.
+   *
+   * @param array $data
+   *
+   * @return \Drupal\simpletest\ObjectArrayDecorator
+   *   The decorated array.
+   */
+  public static function decorate(array $data) {
+    return new static($data);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function offsetExists($offset) {
+    return isset($this->data[$offset]);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function offsetGet($offset) {
+    return $this->data[$offset];
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function offsetSet($offset, $value) {
+    $this->data[$offset] = $value;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function offsetUnset($offset) {
+    unset($this->data[$offset]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function current() {
+    return $this->data[$this->position];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function next() {
+    $this->position++;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function key() {
+    return $this->position;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function valid() {
+    return isset($this->data[$this->position]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewind() {
+    $this->position = 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    return count($this->data);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __get($name) {
+    return $this->data[$name];
+  }
+}
diff --git a/core/modules/simpletest/src/Tests/MinkNodeElementDecoratorTest.php b/core/modules/simpletest/src/Tests/MinkNodeElementDecoratorTest.php
new file mode 100644
index 0000000..909201e
--- /dev/null
+++ b/core/modules/simpletest/src/Tests/MinkNodeElementDecoratorTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\simpletest\MinkNodeElementDecoratorTest.
+ */
+
+namespace Drupal\simpletest\Tests;
+
+use Drupal\simpletest\MinkNodeElementDecorator;
+use Drupal\simpletest\BrowserTestBase;
+
+/**
+ * Tests helper methods provided by the Mink node decorator.
+ *
+ * @group simpletest
+ */
+class MinkNodeElementDecoratorTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('mink_test');
+
+  /**
+   * Test decorator.
+   */
+  public function testDecorator() {
+    $this->drupalGet("/mink-test-1");
+    $element = $this->getSession()->getPage();
+    $container = $element->find("css", "#test-lists-1");
+    $container = new MinkNodeElementDecorator($container);
+
+    // Look for a multiple lists on the page. Should return an array of objects
+    // since that list has children (list items).
+    $this->assertTrue(count($container->ul) > 1, "Array of objects found.");
+
+    // Look for list with single list item. Should be a text value return.
+    $this->assertTrue($container->ul->li == "item1", "Found the single item in the list.");
+
+    // Look for list with single list item. Should be a text value return.
+    $list = $container->ul[0];
+    $this->assertTrue($list->li == "item1", "Found the single item in the list.");
+
+    // Look for a list with multiple list items. Should return an array of
+    // strings.
+    $list = $container->ul[1];
+    $this->assertTrue(count($list->li) > 1);
+    $this->assertTrue($list->li[0] == "item1", "Found the first item in the list.");
+    $this->assertTrue($list->li[1] == "item2", "Found the second item in the list.");
+
+    // Ensure a single child gets decorated. So we can get it's children.
+    $container = $element->find("css", "#test-lists-2");
+    $container = new MinkNodeElementDecorator($container);
+    $this->assertTrue(count($container->ul) == 1, "Our child is an object.");
+    $this->assertTrue($container->ul->li == "item1", "We found the child of the child.");
+  }
+
+}
diff --git a/core/modules/simpletest/src/Tests/SimpleTestTest.php b/core/modules/simpletest/src/Tests/SimpleTestTest.php
index 6e8703a..15659ab 100644
--- a/core/modules/simpletest/src/Tests/SimpleTestTest.php
+++ b/core/modules/simpletest/src/Tests/SimpleTestTest.php
@@ -379,7 +379,7 @@ function getResultFieldSet() {
    * @return
    *   Extracted text.
    */
-  function asText(\SimpleXMLElement $element) {
+  function asText($element) {
     if (!is_object($element)) {
       return $this->fail('The element is not an element.');
     }
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index ac2f0a0..f0f7a19 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -29,6 +29,9 @@
  * Test case for typical Drupal tests.
  *
  * @ingroup testing
+ *
+ * @deprecated Deprecated since Drupal 8.x-dev, to be removed in Drupal 8.0.
+ *   Use \Drupal\simpletest\BrowserTestBase for new browser-based tests.
  */
 abstract class WebTestBase extends TestBase {
 
diff --git a/core/modules/simpletest/tests/modules/mink_test/mink_test.info.yml b/core/modules/simpletest/tests/modules/mink_test/mink_test.info.yml
new file mode 100644
index 0000000..f8a463e
--- /dev/null
+++ b/core/modules/simpletest/tests/modules/mink_test/mink_test.info.yml
@@ -0,0 +1,7 @@
+name: Mink Test
+type: module
+description: 'Provides dummy pages for Mink Simpletest tests.'
+package: Testing
+version: VERSION
+core: 8.x
+hidden: TRUE
diff --git a/core/modules/simpletest/tests/modules/mink_test/mink_test.routing.yml b/core/modules/simpletest/tests/modules/mink_test/mink_test.routing.yml
new file mode 100644
index 0000000..29e496b
--- /dev/null
+++ b/core/modules/simpletest/tests/modules/mink_test/mink_test.routing.yml
@@ -0,0 +1,6 @@
+mink_test.1:
+  path: '/mink-test-1'
+  defaults:
+    _content: '\Drupal\mink_test\Controller\MinkTestController::minkTest1'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/simpletest/tests/modules/mink_test/src/Controller/MinkTestController.php b/core/modules/simpletest/tests/modules/mink_test/src/Controller/MinkTestController.php
new file mode 100644
index 0000000..65b9b81
--- /dev/null
+++ b/core/modules/simpletest/tests/modules/mink_test/src/Controller/MinkTestController.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\mink_test\Controller\MinkTestController.
+ */
+
+namespace Drupal\mink_test\Controller;
+
+/**
+ * Controller routines for mink_test routes.
+ */
+class MinkTestController {
+
+  /**
+   * Outputs some content for testing Mink.
+   *
+   * @return array
+   *   Array of markup.
+   */
+  public function minkTest1() {
+    return array(
+      'test-lists-1' => array(
+        '#type' => 'container',
+        '#attributes' => array(
+          'id' => 'test-lists-1',
+        ),
+        'list1' => array(
+          '#markup' => '<ul><li>item1</li></ul>',
+        ),
+        'list2' => array(
+          '#markup' => '<ul><li>item1</li><li>item2</li></ul>',
+        ),
+      ),
+      'test-lists-2' => array(
+        '#type' => 'container',
+        '#attributes' => array(
+          'id' => 'test-lists-2',
+        ),
+        'list1' => array(
+          '#markup' => '<ul><li>item1</li></ul>',
+        ),
+      ),
+    );
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Form/ElementsLabelsTest.php b/core/modules/system/src/Tests/Form/ElementsLabelsTest.php
index 970f299..b046da0 100644
--- a/core/modules/system/src/Tests/Form/ElementsLabelsTest.php
+++ b/core/modules/system/src/Tests/Form/ElementsLabelsTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\system\Tests\Form;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests form element labels, required markers and associated output.
  *
  * @group Form
  */
-class ElementsLabelsTest extends WebTestBase {
+class ElementsLabelsTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -90,8 +90,8 @@ function testFormLabels() {
 
     // Check title attribute for radios and checkboxes.
     $elements = $this->xpath('//div[@id="edit-form-checkboxes-title-attribute"]');
-    $this->assertEqual($elements[0]['title'], 'Checkboxes test' . ' (' . t('Required') . ')', 'Title attribute found.');
+    $this->assertEqual($elements[0]->getAttribute('title'), 'Checkboxes test' . ' (' . t('Required') . ')', 'Title attribute found.');
     $elements = $this->xpath('//div[@id="edit-form-radios-title-attribute"]');
-    $this->assertEqual($elements[0]['title'], 'Radios test' . ' (' . t('Required') . ')', 'Title attribute found.');
+    $this->assertEqual($elements[0]->getAttribute('title'), 'Radios test' . ' (' . t('Required') . ')', 'Title attribute found.');
   }
 }
diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php
index f113bcf..a49f3d1 100644
--- a/core/modules/system/src/Tests/Form/FormTest.php
+++ b/core/modules/system/src/Tests/Form/FormTest.php
@@ -10,15 +10,16 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Render\Element;
+use Drupal\simpletest\BrowserTestBase;
+use GuzzleHttp\Event\BeforeEvent;
 use Drupal\form_test\Form\FormTestDisabledElementsForm;
-use Drupal\simpletest\WebTestBase;
 
 /**
  * Tests various form element validation mechanisms.
  *
  * @group Form
  */
-class FormTest extends WebTestBase {
+class FormTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -27,6 +28,13 @@ class FormTest extends WebTestBase {
    */
   public static $modules = array('filter', 'form_test', 'file', 'datetime');
 
+  /**
+   * Array of form fields to forge.
+   *
+   * @var array
+   */
+  protected $forge;
+
   function setUp() {
     parent::setUp();
 
@@ -186,7 +194,7 @@ function testRequiredCheckboxesRadio() {
     // Check the page for error messages.
     $errors = $this->xpath('//div[contains(@class, "error")]//li');
     foreach ($errors as $error) {
-      $expected_key = array_search($error[0], $expected);
+      $expected_key = array_search($error->getText(), $expected);
       // If the error message is not one of the expected messages, fail.
       if ($expected_key === FALSE) {
         $this->fail(format_string("Unexpected error message: @error", array('@error' => $error[0])));
@@ -491,11 +499,11 @@ function testDisabledElements() {
       if (isset($form[$key]['#test_hijack_value'])) {
         if (is_array($form[$key]['#test_hijack_value'])) {
           foreach ($form[$key]['#test_hijack_value'] as $subkey => $value) {
-            $edit[$key . '[' . $subkey . ']'] = $value;
+            $edit[$key . '[' . $subkey . ']'] = (string) $value;
           }
         }
         else {
-          $edit[$key] = $form[$key]['#test_hijack_value'];
+          $edit[$key] = (string) $form[$key]['#test_hijack_value'];
         }
       }
     }
@@ -524,7 +532,9 @@ function testDisabledElements() {
       '@expected' => $expected_count,
     )));
 
-    $this->drupalPostForm(NULL, $edit, t('Submit'));
+    $this->forge = $edit;
+    $this->drupalPostForm('form-test/disabled-elements', array(), t('Submit'));
+    $this->assertFalse($this->forge, 'Forged data was sent and reset');
     $returned_values['hijacked'] = Json::decode($this->content);
 
     // Ensure that the returned values match the form's default values in both
@@ -628,9 +638,9 @@ function testDisabledMarkup() {
    */
   function testInputForgery() {
     $this->drupalGet('form-test/input-forgery');
-    $checkbox = $this->xpath('//input[@name="checkboxes[two]"]');
-    $checkbox[0]['value'] = 'FORGERY';
+    $this->forge = array('checkboxes' => array('one' => 'one', 'two' => 'FORGERY'));
     $this->drupalPostForm(NULL, array('checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE), t('Submit'));
+    $this->forge = FALSE;
     $this->assertText('An illegal choice has been detected.', 'Input forgery was detected.');
   }
 
@@ -665,4 +675,19 @@ public function testFormStateDatabaseConnection() {
     $this->drupalPostForm('form-test/form_state-database', array(), t('Submit'));
     $this->assertText('Database connection found');
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onBefore(BeforeEvent $event) {
+    parent::onBefore($event);
+    // Forge fields.
+    if ($this->forge && ($request = $event->getRequest()) && ($body = $request->getBody())) {
+      foreach ($this->forge as $key => $value) {
+        $body->setField($key, $value);
+      }
+      $this->forge = FALSE;
+    }
+  }
+
 }
diff --git a/core/modules/system/src/Tests/Menu/LocalActionTest.php b/core/modules/system/src/Tests/Menu/LocalActionTest.php
index bcc443b..6e7a5d8 100644
--- a/core/modules/system/src/Tests/Menu/LocalActionTest.php
+++ b/core/modules/system/src/Tests/Menu/LocalActionTest.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\system\Tests\Menu;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests local actions derived from router and added/altered via hooks.
  *
  * @group Menu
  */
-class LocalActionTest extends WebTestBase {
+class LocalActionTest extends BrowserTestBase {
 
   /**
    * {@inheritdoc}
@@ -48,7 +48,7 @@ protected function assertLocalAction(array $actions) {
     $index = 0;
     foreach ($actions as $href => $title) {
       $this->assertEqual((string) $elements[$index], $title);
-      $this->assertEqual($elements[$index]['href'], url($href));
+      $this->assertEqual($elements[$index]->getAttribute('href'), url($href));
       $index++;
     }
   }
diff --git a/core/modules/system/src/Tests/Menu/MenuTestBase.php b/core/modules/system/src/Tests/Menu/MenuTestBase.php
index fc896d8..be7cdeb 100644
--- a/core/modules/system/src/Tests/Menu/MenuTestBase.php
+++ b/core/modules/system/src/Tests/Menu/MenuTestBase.php
@@ -8,9 +8,9 @@
 namespace Drupal\system\Tests\Menu;
 
 use Drupal\Component\Utility\String;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\BrowserTestBase;
 
-abstract class MenuTestBase extends WebTestBase {
+abstract class MenuTestBase extends BrowserTestBase {
 
   /**
    * Assert that a given path shows certain breadcrumb links.
@@ -140,9 +140,9 @@ protected function getBreadcrumbParts() {
     if (!empty($elements)) {
       foreach ($elements as $element) {
         $parts[] = array(
-          'text' => (string) $element,
-          'href' => (string) $element['href'],
-          'title' => (string) $element['title'],
+          'text' => $element->getText(),
+          'href' => $element->getAttribute('href'),
+          'title' => $element->getAttribute('title'),
         );
       }
     }
diff --git a/core/modules/system/src/Tests/Pager/PagerTest.php b/core/modules/system/src/Tests/Pager/PagerTest.php
index 1f3fe4d..0ebd10a 100644
--- a/core/modules/system/src/Tests/Pager/PagerTest.php
+++ b/core/modules/system/src/Tests/Pager/PagerTest.php
@@ -7,14 +7,15 @@
 
 namespace Drupal\system\Tests\Pager;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\MinkNodeElementDecorator;
+use Drupal\simpletest\BrowserTestBase;
 
 /**
  * Tests pager functionality.
  *
  * @group Pager
  */
-class PagerTest extends WebTestBase {
+class PagerTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -131,34 +132,34 @@ protected function assertPagerItems($current_page) {
   /**
    * Asserts that an element has a given class.
    *
-   * @param \SimpleXMLElement $element
+   * @param \Drupal\simpletest\MinkNodeElementDecorator $element
    *   The element to test.
    * @param string $class
    *   The class to assert.
    * @param string $message
    *   (optional) A verbose message to output.
    */
-  protected function assertClass(\SimpleXMLElement $element, $class, $message = NULL) {
+  protected function assertClass(MinkNodeElementDecorator $element, $class, $message = NULL) {
     if (!isset($message)) {
       $message = "Class .$class found.";
     }
-    $this->assertTrue(strpos($element['class'], $class) !== FALSE, $message);
+    $this->assertTrue(strpos($element->getAttribute('class'), $class) !== FALSE, $message);
   }
 
   /**
    * Asserts that an element does not have a given class.
    *
-   * @param \SimpleXMLElement $element
+   * @param \Behat\Mink\Element\NodeElement $element
    *   The element to test.
    * @param string $class
    *   The class to assert.
    * @param string $message
    *   (optional) A verbose message to output.
    */
-  protected function assertNoClass(\SimpleXMLElement $element, $class, $message = NULL) {
+  protected function assertNoClass(MinkNodeElementDecorator $element, $class, $message = NULL) {
     if (!isset($message)) {
       $message = "Class .$class not found.";
     }
-    $this->assertTrue(strpos($element['class'], $class) === FALSE, $message);
+    $this->assertTrue(strpos($element->getAttribute('class'), $class) === FALSE, $message);
   }
 }
