diff --git a/core/modules/tour/src/Tests/TourCacheTagsTest.php b/core/modules/tour/tests/src/Functional/TourCacheTagsTest.php
similarity index 95%
rename from core/modules/tour/src/Tests/TourCacheTagsTest.php
rename to core/modules/tour/tests/src/Functional/TourCacheTagsTest.php
index 4e0972d..8e4e2ae 100644
--- a/core/modules/tour/src/Tests/TourCacheTagsTest.php
+++ b/core/modules/tour/tests/src/Functional/TourCacheTagsTest.php
@@ -1,9 +1,9 @@
assertTourTips();
+ *
+ * // Advanced example. The following would be used for multipage or targeting
+ * // a specific subset of tips.
+ * $tips = [];
+ * $tips[] = ['data-id' => 'foo'];
+ * $tips[] = ['data-id' => 'bar'];
+ * $tips[] = ['data-class' => 'baz'];
+ * $this->assertTourTips($tips);
+ * @endcode
+ */
+ public function assertTourTips($tips = []) {
+ // Get the rendered tips and their data-id and data-class attributes.
+ if (empty($tips)) {
+ // Tips are rendered as
elements inside .
+ $rendered_tips = $this->xpath('//ol[@id = "tour"]//li[starts-with(@class, "tip")]');
+ foreach ($rendered_tips as $rendered_tip) {
+ $tips[] = [
+ 'data-id' => $rendered_tip->getAttribute('data-id'),
+ 'data-class' => $rendered_tip->getAttribute('data-class'),
+ ];
+ }
+ }
+
+ // If the tips are still empty we need to fail.
+ if (empty($tips)) {
+ $this->fail('Could not find tour tips on the current page.');
+ }
+ else {
+ // Check for corresponding page elements.
+ $total = 0;
+ $modals = 0;
+ foreach ($tips as $tip) {
+ if (!empty($tip['data-id'])) {
+ $elements = \PHPUnit_Util_XML::cssSelect('#' . $tip['data-id'], TRUE, $this->getTextContent(), TRUE);
+ $this->assertCount(1, $elements);
+ }
+ elseif (!empty($tip['data-class'])) {
+ $elements = \PHPUnit_Util_XML::cssSelect('.' . $tip['data-class'], TRUE, $this->getTextContent(), TRUE);
+ $this->assertNotEmpty($elements);
+ }
+ else {
+ // It's a modal.
+ $modals++;
+ }
+ $total++;
+ }
+ $this->assertTrue(TRUE, "Total $total Tips tested of which $modals modal(s).");
+ }
+ }
+
+}
diff --git a/core/modules/tour/tests/src/Functional/TourTestBasic.php b/core/modules/tour/tests/src/Functional/TourTestBasic.php
new file mode 100644
index 0000000..2ed93a7
--- /dev/null
+++ b/core/modules/tour/tests/src/Functional/TourTestBasic.php
@@ -0,0 +1,70 @@
+ [
+ * ['data-id' => 'foo'],
+ * ['data-class' => 'bar'],
+ * ],
+ * ];
+ * @endcode
+ */
+ protected $tips = array();
+
+ /**
+ * An admin user with administrative permissions for tour.
+ *
+ * @var \Drupal\user\UserInterface
+ */
+ protected $adminUser;
+
+ /**
+ * The permissions required for a logged in user to test tour tips.
+ *
+ * @var array
+ * A list of permissions.
+ */
+ protected $permissions = ['access tour'];
+
+ protected function setUp() {
+ parent::setUp();
+
+ // Make sure we are using distinct default and administrative themes for the
+ // duration of these tests.
+ $this->container->get('theme_handler')->install(['bartik', 'seven']);
+ $this->config('system.theme')
+ ->set('default', 'bartik')
+ ->set('admin', 'seven')
+ ->save();
+
+ $this->permissions[] = 'view the administration theme';
+
+ // Create an admin user to view tour tips.
+ $this->adminUser = $this->drupalCreateUser($this->permissions);
+ $this->drupalLogin($this->adminUser);
+ }
+
+ /**
+ * A simple tip test.
+ */
+ public function testTips() {
+ foreach ($this->tips as $path => $attributes) {
+ $this->drupalGet($path);
+ $this->assertTourTips($attributes);
+ }
+ }
+
+}
diff --git a/core/tests/Drupal/FunctionalTests/PageCacheTagsTestBase.php b/core/tests/Drupal/FunctionalTests/PageCacheTagsTestBase.php
new file mode 100644
index 0000000..f783594
--- /dev/null
+++ b/core/tests/Drupal/FunctionalTests/PageCacheTagsTestBase.php
@@ -0,0 +1,54 @@
+config('system.performance')
+ ->set('cache.page.max_age', 3600)
+ ->save();
+ }
+
+ /**
+ * Verify that when loading a given page, it's a page cache hit or miss.
+ *
+ * @param \Drupal\Core\Url $url
+ * The page for this URL will be loaded.
+ * @param string $hit_or_miss
+ * 'HIT' if a page cache hit is expected, 'MISS' otherwise.
+ * @param array|false $tags
+ * (optional) When expecting a page cache hit, you may optionally specify an
+ * array of expected cache tags. While FALSE, the cache tags will not be
+ * verified. Defaults to FALSE.
+ */
+ protected function verifyPageCache(Url $url, $hit_or_miss, $tags = FALSE) {
+ $this->drupalGet($url);
+ $message = "Page cache $hit_or_miss for {$url->toString()}.";
+ self::assertEquals($this->drupalGetHeader('X-Drupal-Cache'), $hit_or_miss, $message);
+
+ if ($hit_or_miss === 'HIT' && is_array($tags)) {
+ $absolute_url = $url->setAbsolute()->toString();
+ $cid_parts = [$absolute_url, 'html'];
+ $cid = implode(':', $cid_parts);
+ $cache_entry = $this->container->get('cache.render')->get($cid);
+ sort($cache_entry->tags);
+ $tags = array_unique($tags);
+ sort($tags);
+ self::assertSame($cache_entry->tags, $tags);
+ }
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/WebAssert.php b/core/tests/Drupal/Tests/WebAssert.php
index 6156dda..ffe9f79 100644
--- a/core/tests/Drupal/Tests/WebAssert.php
+++ b/core/tests/Drupal/Tests/WebAssert.php
@@ -248,7 +248,7 @@ public function linkExists($label, $index = 0, $message = '') {
*/
public function linkNotExists($label, $message = '') {
$message = ($message ? $message : strtr('Link with label %label found.', ['%label' => $label]));
- $links = $this->session->getPage()->findAll('named', ['link', $label]);
+ $links = $this->session->getPage()->findAll('named_exact', ['link', $label]);
$this->assert(empty($links), $message);
}