diff --git a/core/modules/node/src/Tests/Views/FrontPageTest.php b/core/modules/node/src/Tests/Views/FrontPageTest.php
index eb970d6..7ff8791 100644
--- a/core/modules/node/src/Tests/Views/FrontPageTest.php
+++ b/core/modules/node/src/Tests/Views/FrontPageTest.php
@@ -7,6 +7,11 @@
 
 namespace Drupal\node\Tests\Views;
 
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Url;
+use Drupal\node\Entity\Node;
+use Drupal\system\Tests\Cache\AssertPageCacheTagsTrait;
+use Drupal\views\Tests\AssertViewsCacheTagsTrait;
 use Drupal\views\Tests\ViewTestBase;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Views;
@@ -18,6 +23,14 @@
  */
 class FrontPageTest extends ViewTestBase {
 
+  use AssertPageCacheTagsTrait;
+  use AssertViewsCacheTagsTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $dumpHeaders = TRUE;
+
   /**
    * The entity storage for nodes.
    *
@@ -173,4 +186,148 @@ public function testAdminFrontPage() {
     $this->assertPattern('/class=".+view-frontpage/', 'Frontpage view was rendered');
   }
 
+  /**
+   * Tests the cache tags when using the "none" cache plugin.
+   */
+  public function testCacheTagsWithCachePluginNone() {
+    $this->enablePageCaching();
+    $this->assertFrontPageViewCacheTags(FALSE);
+  }
+
+  /**
+   * Tests the cache tags when using the "tag" cache plugin.
+   */
+  public function testCacheTagsWithCachePluginTag() {
+    $this->enablePageCaching();
+
+    // Enable tag-based caching on the view.
+    $view = Views::getView('frontpage');
+    $view->setDisplay('page_1');
+    $view->display_handler->overrideOption('cache', [
+      'type' => 'tag',
+    ]);
+    $view->save();
+
+    $this->assertFrontPageViewCacheTags(TRUE);
+  }
+
+  /**
+   * Tests the cache tags when using the "time" cache plugin.
+   */
+  public function testCacheTagsWithTimePluginTag() {
+    $this->enablePageCaching();
+
+    $view = Views::getView('frontpage');
+    $view->setDisplay('page_1');
+    $view->display_handler->overrideOption('cache', [
+      'type' => 'time',
+      'options' => [
+        'results_lifespan' => 3600,
+        'output_lifespan' => 3600,
+      ],
+    ]);
+    $view->save();
+
+    $this->assertFrontPageViewCacheTags(TRUE);
+  }
+
+  /**
+   * Tests the cache tags on the front page.
+   *
+   * @param bool $assert_views_caches
+   *   Whether to check Views' result & output caches.
+   */
+  protected function assertFrontPageViewCacheTags($assert_views_caches) {
+    $view = Views::getView('frontpage');
+    $view->setDisplay('page_1');
+
+    // Test before there are any nodes.
+    $empty_node_listing_cache_tags = [
+      'config:views.view.frontpage',
+      'node_list',
+    ];
+    if ($assert_views_caches) {
+      $this->assertViewsCacheTags(
+        $view,
+        $empty_node_listing_cache_tags,
+        Cache::mergeTags($empty_node_listing_cache_tags, ['rendered'])
+      );
+    }
+    $this->assertPageCacheTags(
+      Url::fromRoute('view.frontpage.page_1'),
+      Cache::mergeTags($empty_node_listing_cache_tags, ['rendered'])
+    );
+
+    // Create some nodes on the frontpage view. Add more than 10 nodes in order
+    // to enable paging.
+    $this->drupalCreateContentType(['type' => 'article']);
+    for ($i = 0; $i < 15; $i++) {
+      $node = Node::create([
+        'body' => [
+          [
+            'value' => $this->randomMachineName(32),
+            'format' => filter_default_format(),
+          ]
+        ],
+        'type' => 'article',
+        'created' => $i,
+        'title' => $this->randomMachineName(8),
+        'nid' => $i + 1,
+      ]);
+      $node->enforceIsNew(TRUE);
+      $node->save();
+    }
+
+    // First page.
+    $first_page_result_cache_tags = [
+      'config:views.view.frontpage',
+      'node_list',
+      'node:6', 'node:7', 'node:8', 'node:9', 'node:10',
+      'node:11', 'node:12', 'node:13', 'node:14', 'node:15',
+    ];
+    $first_page_output_cache_tags = Cache::mergeTags($first_page_result_cache_tags, [
+      'config:filter.format.plain_text',
+      'node_view',
+      'user_view',
+      'user:0',
+      'rendered',
+    ]);
+    $view->setDisplay('page_1');
+    if ($assert_views_caches) {
+      $this->assertViewsCacheTags(
+        $view,
+        $first_page_result_cache_tags,
+        $first_page_output_cache_tags
+      );
+    }
+    $this->assertPageCacheTags(
+      Url::fromRoute('view.frontpage.page_1'),
+      $first_page_output_cache_tags
+    );
+
+    // Second page.
+    $this->assertPageCacheTags(Url::fromRoute('view.frontpage.page_1', [], ['query' => ['page' => 1]]), [
+      // The cache tags for the listed nodes.
+      'node:1', 'node:2', 'node:3', 'node:4', 'node:5',
+      // The rest.
+      'config:filter.format.plain_text',
+      'config:views.view.frontpage',
+      'node_list',
+      'node_view',
+      'user_view',
+      'user:0',
+      'rendered',
+    ]);
+
+    // Let's update a node title on the first page and ensure that the page
+    // cache entry invalidates.
+    $node = Node::load(10);
+    $title = $node->getTitle() . 'a';
+    $node->setTitle($title);
+    $node->save();
+
+    $this->drupalGet(Url::fromRoute('view.frontpage.page_1'));
+    $this->assertText($title);
+  }
+
 }
diff --git a/core/modules/system/src/Tests/Cache/AssertPageCacheTagsTrait.php b/core/modules/system/src/Tests/Cache/AssertPageCacheTagsTrait.php
new file mode 100644
index 0000000..6fed903
--- /dev/null
+++ b/core/modules/system/src/Tests/Cache/AssertPageCacheTagsTrait.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Cache\AssertPageCacheTagsTrait.
+ */
+
+namespace Drupal\system\Tests\Cache;
+
+use Drupal\Core\Url;
+
+/**
+ * Provides test assertions for testing page-level cache tags.
+ *
+ * Can be used by test classes that extend \Drupal\simpletest\WebTestBase.
+ */
+trait AssertPageCacheTagsTrait {
+
+  /**
+   * Enables page caching.
+   */
+  protected function enablePageCaching() {
+    $config = $this->config('system.performance');
+    $config->set('cache.page.use_internal', 1);
+    $config->set('cache.page.max_age', 300);
+    $config->save();
+  }
+
+  /**
+   * Asserts page cache miss, then hit for the given URL; checks cache tags.
+   *
+   * @param \Drupal\Core\Url $url
+   *   The URL to test.
+   * @param string[] $expected_tags
+   *   The expected cache tags for the given URL.
+   */
+  protected function assertPageCacheTags(Url $url, array $expected_tags) {
+    $absolute_url = $url->setAbsolute()->toString();
+    sort($expected_tags);
+
+    // Assert cache miss + expected cache tags.
+    $this->drupalGet($absolute_url);
+    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
+    $actual_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
+    sort($actual_tags);
+    $this->assertIdentical($actual_tags, $expected_tags);
+
+    // Assert cache hit + expected cache tags.
+    $this->drupalGet($absolute_url);
+    $actual_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
+    sort($actual_tags);
+    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
+    $this->assertIdentical($actual_tags, $expected_tags);
+
+    // Assert page cache item + expected cache tags.
+    $cid_parts = array($url->setAbsolute()->toString(), 'html');
+    $cid = implode(':', $cid_parts);
+    $cache_entry = \Drupal::cache('render')->get($cid);
+    sort($cache_entry->tags);
+    $this->assertEqual($cache_entry->tags, $expected_tags);
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
index 2da2e8d..9c69f0a 100644
--- a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -7,9 +7,7 @@
 
 namespace Drupal\system\Tests\Cache;
 
-use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
-use Drupal\Core\Cache\Cache;
 
 /**
  * Enables the page cache and tests its cache tags in various scenarios.
@@ -21,6 +19,8 @@
  */
 class PageCacheTagsIntegrationTest extends WebTestBase {
 
+  use AssertPageCacheTagsTrait;
+
   protected $profile = 'standard';
 
   protected $dumpHeaders = TRUE;
@@ -31,10 +31,7 @@ class PageCacheTagsIntegrationTest extends WebTestBase {
   protected function setUp() {
     parent::setUp();
 
-    $config = $this->config('system.performance');
-    $config->set('cache.page.use_internal', 1);
-    $config->set('cache.page.max_age', 300);
-    $config->save();
+    $this->enablePageCaching();
   }
 
   /**
@@ -71,7 +68,7 @@ function testPageCacheTags() {
     ));
 
     // Full node page 1.
-    $this->verifyPageCacheTags($node_1->urlInfo(), array(
+    $this->assertPageCacheTags($node_1->urlInfo(), [
       'rendered',
       'block_view',
       'config:block_list',
@@ -100,10 +97,10 @@ function testPageCacheTags() {
       'config:system.menu.footer',
       'config:system.menu.main',
       'config:system.site',
-    ));
+    ]);
 
     // Full node page 2.
-    $this->verifyPageCacheTags($node_2->urlInfo(), array(
+    $this->assertPageCacheTags($node_2->urlInfo(), [
       'rendered',
       'block_view',
       'config:block_list',
@@ -117,6 +114,7 @@ function testPageCacheTags() {
       'config:block.block.bartik_main_menu',
       'config:block.block.bartik_account_menu',
       'block_plugin:system_breadcrumb_block',
+      'config:views.view.comments_recent',
       'block_plugin:system_main_block',
       'block_plugin:system_menu_block__account',
       'block_plugin:system_menu_block__main',
@@ -134,36 +132,9 @@ function testPageCacheTags() {
       'config:system.menu.footer',
       'config:system.menu.main',
       'config:system.site',
-    ));
-  }
-
-  /**
-   * Fills page cache for the given path, verify cache tags on page cache hit.
-   *
-   * @param \Drupal\Core\Url $url
-   *   The url
-   * @param $expected_tags
-   *   The expected cache tags for the page cache entry of the given $path.
-   */
-  protected function verifyPageCacheTags(Url $url, $expected_tags) {
-    // @todo Change ->drupalGet() calls to just pass $url when
-    //   https://www.drupal.org/node/2350837 gets committed
-    sort($expected_tags);
-    $this->drupalGet($url->setAbsolute()->toString());
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
-    $actual_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
-    sort($actual_tags);
-    $this->assertIdentical($actual_tags, $expected_tags);
-    $this->drupalGet($url->setAbsolute()->toString());
-    $actual_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
-    sort($actual_tags);
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
-    $this->assertIdentical($actual_tags, $expected_tags);
-    $cid_parts = array($url->setAbsolute()->toString(), 'html');
-    $cid = implode(':', $cid_parts);
-    $cache_entry = \Drupal::cache('render')->get($cid);
-    sort($cache_entry->tags);
-    $this->assertEqual($cache_entry->tags, $expected_tags);
+      'comment_list',
+      'node_list',
+    ]);
   }
 
 }
diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
index f66d3e6..b14c496 100644
--- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
+++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
@@ -191,7 +191,7 @@ public function cacheSet($type) {
         // that is used to render the view for this request and rendering does
         // not happen twice.
         $this->storage = $this->view->display_handler->output = $this->renderer->getCacheableRenderArray($output);
-        \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), $this->getCacheTags());
+        \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), Cache::mergeTags($this->storage['#cache']['tags'], ['rendered']));
         break;
     }
   }
@@ -239,9 +239,6 @@ public function cacheGet($type) {
 
   /**
    * Clear out cached data for a view.
-   *
-   * We're just going to nuke anything related to the view, regardless of display,
-   * to be sure that we catch everything. Maybe that's a bad idea.
    */
   public function cacheFlush() {
     Cache::invalidateTags($this->view->storage->getCacheTags());
@@ -301,12 +298,18 @@ public function generateResultsKey() {
         'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
         'base_url' => $GLOBALS['base_url'],
       );
-      foreach (array('exposed_info', 'page', 'sort', 'order', 'items_per_page', 'offset') as $key) {
+      foreach (array('exposed_info', 'sort', 'order') as $key) {
         if ($this->view->getRequest()->query->has($key)) {
           $key_data[$key] = $this->view->getRequest()->query->get($key);
         }
       }
 
+      $key_data['pager'] = [
+        'page' => $this->view->getCurrentPage(),
+        'items_per_page' => $this->view->getItemsPerPage(),
+        'offset' => $this->view->getOffset(),
+      ];
+
       $this->resultsKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':results:' . hash('sha256', serialize($key_data));
     }
 
@@ -343,18 +346,21 @@ public function generateOutputKey() {
    * @return string[]
    *   An array of cache tags based on the current view.
    */
-  protected function getCacheTags() {
+  public function getCacheTags() {
     $tags = $this->view->storage->getCacheTags();
 
+    // The list cache tags for the entity types listed in this view.
     $entity_information = $this->view->query->getEntityTableInfo();
 
     if (!empty($entity_information)) {
       // Add the list cache tags for each entity type used by this view.
-      foreach (array_keys($entity_information) as $entity_type) {
-        $tags = Cache::mergeTags($tags, \Drupal::entityManager()->getDefinition($entity_type)->getListCacheTags());
+      foreach ($entity_information as $table => $metadata) {
+        $tags = Cache::mergeTags($tags, \Drupal::entityManager()->getDefinition($metadata['entity_type'])->getListCacheTags());
       }
     }
 
+    $tags = Cache::mergeTags($tags, $this->view->getQuery()->getCacheTags());
+
     return $tags;
   }
 
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
index 1ba38b3..5ad571f 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -2115,6 +2115,15 @@ public function render() {
       '#post_render_cache' => &$this->view->element['#post_render_cache'],
     );
 
+    if (!isset($element['#cache'])) {
+      $element['#cache'] = [];
+    }
+    $element['#cache'] += ['tags' => []];
+
+    // If the output is a render array, add cache tags, regardless of whether
+    // caching is enabled or not; cache tags must always be set.
+    $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $this->view->getCacheTags());
+
     return $element;
   }
 
diff --git a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
index 1c254dc..a206d03 100644
--- a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
+++ b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
@@ -312,6 +312,13 @@ public function getEntityTableInfo() {
     return $entity_tables;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return [];
+  }
+
 }
 
 /**
diff --git a/core/modules/views/src/Plugin/views/query/Sql.php b/core/modules/views/src/Plugin/views/query/Sql.php
index 47d1979..1be46f9 100644
--- a/core/modules/views/src/Plugin/views/query/Sql.php
+++ b/core/modules/views/src/Plugin/views/query/Sql.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Plugin\views\query;
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
@@ -1537,6 +1538,23 @@ function loadEntities(&$results) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    $tags = [];
+    // Add cache tags for each row, if there is an entity associated with it.
+    if (!$this->hasAggregate) {
+      foreach ($this->view->result as $row)  {
+        if ($row->_entity) {
+          $tags = Cache::mergeTags($row->_entity->getCacheTags(), $tags);
+        }
+      }
+    }
+
+    return $tags;
+  }
+
   public function addSignature(ViewExecutable $view) {
     $view->query->addField(NULL, "'" . $view->storage->id() . ':' . $view->current_display . "'", 'view_name');
   }
diff --git a/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
new file mode 100644
index 0000000..6ab4e12
--- /dev/null
+++ b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Tests\AssertViewsCacheTagsTrait.
+ */
+
+namespace Drupal\views\Tests;
+
+use Drupal\views\ViewExecutable;
+
+trait AssertViewsCacheTagsTrait {
+
+
+  /**
+   * Asserts a view's result & output cache items' cache tags.
+   *
+   * @param \Drupal\views\ViewExecutable $view
+   *   The view to test, must have caching enabled.
+   * @param null|string[] $expected_results_cache
+   *   NULL when expecting no results cache item, a set of cache tags expected
+   *   to be set on the results cache item otherwise.
+   * @param null|string[] $expected_output_cache
+   *   NULL when expecting no output cache item, a set of cache tags expected to
+   *   be set on the output cache item otherwise.
+   */
+  protected function assertViewsCacheTags(ViewExecutable $view, $expected_results_cache, $expected_output_cache) {
+    $output = $view->preview();
+
+    /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */
+    $cache_plugin = $view->display_handler->getPlugin('cache');
+
+    // Results cache.
+    $results_cache_item = \Drupal::cache('data')->get($cache_plugin->generateResultsKey());
+    if (is_array($expected_results_cache)) {
+      $this->assertTrue($results_cache_item, 'Results cache item found.');
+      if ($results_cache_item) {
+        sort($expected_results_cache);
+        $this->assertEqual($results_cache_item->tags, $expected_results_cache);
+      }
+    }
+    else {
+      $this->assertFalse($results_cache_item, 'Results cache item not found.');
+    }
+
+    // Output cache.
+    $output_cache_item = \Drupal::cache('render')->get($cache_plugin->generateOutputKey());
+    if (is_array($expected_output_cache)) {
+      $this->assertTrue($output_cache_item, 'Output cache item found.');
+      if ($output_cache_item) {
+        sort($expected_output_cache);
+        $this->assertEqual($output_cache_item->tags, $expected_output_cache);
+      }
+    }
+    else {
+      $this->assertFalse($output_cache_item, 'Output cache item not found.');
+    }
+
+    $view->destroy();
+
+    return $output;
+  }
+
+}
diff --git a/core/modules/views/src/Tests/GlossaryTest.php b/core/modules/views/src/Tests/GlossaryTest.php
index c9cb84a..c661839 100644
--- a/core/modules/views/src/Tests/GlossaryTest.php
+++ b/core/modules/views/src/Tests/GlossaryTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Url;
+use Drupal\system\Tests\Cache\AssertPageCacheTagsTrait;
 use Drupal\views\Views;
 
 /**
@@ -18,6 +19,9 @@
  */
 class GlossaryTest extends ViewTestBase {
 
+  use AssertPageCacheTagsTrait;
+  use AssertViewsCacheTagsTrait;
+
   /**
    * Modules to enable.
    *
@@ -39,6 +43,7 @@ public function testGlossaryView() {
       'a' => 3,
       'l' => 6,
     );
+    $nodes_by_char = [];
     foreach ($nodes_per_char as $char => $count) {
       $setting = array(
         'type' => $type->id()
@@ -46,7 +51,8 @@ public function testGlossaryView() {
       for ($i = 0; $i < $count; $i++) {
         $node = $setting;
         $node['title'] = $char . $this->randomString(3);
-        $this->drupalCreateNode($node);
+        $node = $this->drupalCreateNode($node);
+        $nodes_by_char[$char][] = $node;
       }
     }
 
@@ -77,6 +83,16 @@ public function testGlossaryView() {
       $result_count = trim(str_replace(array('|', '(', ')'), '', (string) $result[0]));
       $this->assertEqual($result_count, $count, 'The expected number got rendered.');
     }
+
+    // Verify cache tags.
+    $this->enablePageCaching();
+    $this->assertPageCacheTags(Url::fromRoute('view.glossary.page_1'), [
+      'config:views.view.glossary',
+      'node:' . $nodes_by_char['a'][0]->id(), 'node:' . $nodes_by_char['a'][1]->id(), 'node:' . $nodes_by_char['a'][2]->id(),
+      'node_list',
+      'user_list',
+      'rendered',
+    ]);
   }
 
 }
diff --git a/core/modules/views/src/Tests/Plugin/CacheTest.php b/core/modules/views/src/Tests/Plugin/CacheTest.php
index 913aab2..d730abf 100644
--- a/core/modules/views/src/Tests/Plugin/CacheTest.php
+++ b/core/modules/views/src/Tests/Plugin/CacheTest.php
@@ -44,7 +44,6 @@ protected function setUp() {
    * @see views_plugin_cache_time
    */
   public function testTimeResultCaching() {
-    // Create a basic result which just 2 results.
     $view = Views::getView('test_cache');
     $view->setDisplay();
     $view->display_handler->overrideOption('cache', array(
@@ -55,6 +54,7 @@ public function testTimeResultCaching() {
       )
     ));
 
+    // Test the default (non-paged) display.
     $this->executeView($view);
     // Verify the result.
     $this->assertEqual(5, count($view->result), 'The number of returned rows match.');
@@ -67,7 +67,19 @@ public function testTimeResultCaching() {
     );
     db_insert('views_test_data')->fields($record)->execute();
 
-    // The Result should be the same as before, because of the caching.
+    // The result should be the same as before, because of the caching. (Note
+    // that views_test_data records don't have associated cache tags, and hence
+    // the results cache items aren't invalidated.)
+    $view->destroy();
+    $this->executeView($view);
+    // Verify the result.
+    $this->assertEqual(5, count($view->result), 'The number of returned rows match.');
+  }
+
+  /**
+   * Tests result caching with a pager.
+   */
+  public function testTimeResultCachingWithPager() {
     $view = Views::getView('test_cache');
     $view->setDisplay();
     $view->display_handler->overrideOption('cache', array(
@@ -78,9 +90,31 @@ public function testTimeResultCaching() {
       )
     ));
 
+    $mapping = ['views_test_data_name' => 'name'];
+
+    $view->setDisplay('page_1');
+    $view->setCurrentPage(0);
     $this->executeView($view);
-    // Verify the result.
-    $this->assertEqual(5, count($view->result), 'The number of returned rows match.');
+    $this->assertIdenticalResultset($view, [['name' => 'John'], ['name' => 'George']], $mapping);
+    $view->destroy();
+
+    $view->setDisplay('page_1');
+    $view->setCurrentPage(1);
+    $this->executeView($view);
+    $this->assertIdenticalResultset($view, [['name' => 'Ringo'], ['name' => 'Paul']], $mapping);
+    $view->destroy();
+
+    $view->setDisplay('page_1');
+    $view->setCurrentPage(0);
+    $this->executeView($view);
+    $this->assertIdenticalResultset($view, [['name' => 'John'], ['name' => 'George']], $mapping);
+    $view->destroy();
+
+    $view->setDisplay('page_1');
+    $view->setCurrentPage(2);
+    $this->executeView($view);
+    $this->assertIdenticalResultset($view, [['name' => 'Meredith']], $mapping);
+    $view->destroy();
   }
 
   /**
@@ -149,7 +183,8 @@ function testHeaderStorage() {
     drupal_render($output);
     $this->assertTrue(in_array('views_test_data/test', $output['#attached']['library']), 'Make sure libraries are added for cached views.');
     $this->assertEqual(['foo' => 'bar'], $output['#attached']['drupalSettings'], 'Make sure drupalSettings are added for cached views.');
-    $this->assertEqual(['views_test_data:1'], $output['#cache']['tags']);
+    // Note: views_test_data_views_pre_render() adds some cache tags.
+    $this->assertEqual(['config:views.view.test_cache_header_storage', 'views_test_data:1'], $output['#cache']['tags']);
     $this->assertEqual(['views_test_data_post_render_cache' => [['foo' => 'bar']]], $output['#post_render_cache']);
     $this->assertFalse(!empty($view->build_info['pre_render_called']), 'Make sure hook_views_pre_render is not called for the cached view.');
   }
diff --git a/core/modules/views/src/Tests/RenderCacheIntegrationTest.php b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
new file mode 100644
index 0000000..ff46a75
--- /dev/null
+++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Tests\RenderCacheIntegrationTest.
+ */
+
+namespace Drupal\views\Tests;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\views\Views;
+
+/**
+ * Tests the general integration between Views and the render cache.
+ *
+ * @group views
+ */
+class RenderCacheIntegrationTest extends ViewUnitTestBase {
+
+  use AssertViewsCacheTagsTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $testViews = ['entity_test_fields', 'entity_test_row'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['entity_test', 'user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('entity_test');
+    $this->installEntitySchema('user');
+  }
+
+  /**
+   * Tests cache tags on output & result cache items for a field-based view.
+   */
+  public function testFieldBasedView() {
+    $view = Views::getview('entity_test_fields');
+    $view->getDisplay()->overrideOption('cache', [
+      'type' => 'tag',
+    ]);
+
+
+    // Empty result (no entities yet).
+    $base_tags =  ['config:views.view.entity_test_fields', 'entity_test_list'];
+    $this->assertViewsCacheTags($view, $base_tags, Cache::mergeTags($base_tags, ['rendered']));
+
+
+    // Non-empty result (1 entity).
+    $entities[] = $entity = EntityTest::create();
+    $entity->save();
+
+    $tags_with_entity = Cache::mergeTags($base_tags, $entities[0]->getCacheTags());
+    $this->assertViewsCacheTags($view, $tags_with_entity, Cache::mergeTags($tags_with_entity, ['rendered']));
+
+
+    // Paged result (more entities than the items-per-page limit).
+    for ($i = 0; $i < 5; $i++) {
+      $entities[] = $entity = EntityTest::create();
+      $entity->save();
+    }
+    // Page 1.
+    $tags_page_1 = Cache::mergeTags($base_tags, $entities[1]->getCacheTags(), $entities[2]->getCacheTags(), $entities[3]->getCacheTags(), $entities[4]->getCacheTags(), $entities[5]->getCacheTags());
+    $this->assertViewsCacheTags($view, $tags_page_1, Cache::mergeTags($tags_page_1, ['rendered']));
+    $view->destroy();
+    // Page 2.
+    $view->setCurrentPage(1);
+    $tags_page_2 = Cache::mergeTags($base_tags, $entities[0]->getCacheTags());
+    $this->assertViewsCacheTags($view, $tags_page_2, Cache::mergeTags($tags_page_2, ['rendered']));
+    $view->destroy();
+
+
+    // Ensure that invalidation works on both pages.
+    $view->setCurrentPage(1);
+    $entities[0]->name->value = $random_name = $this->randomMachineName();
+    $entities[0]->save();
+    $output = $this->assertViewsCacheTags($view, $tags_page_2, Cache::mergeTags($tags_page_2, ['rendered']));
+    $this->assertTrue(strpos($output['#markup'], $random_name) !== FALSE);
+    $view->destroy();
+
+    $view->setCurrentPage(0);
+    $entities[1]->name->value = $random_name = $this->randomMachineName();
+    $entities[1]->save();
+    $output = $this->assertViewsCacheTags($view, $tags_page_1, Cache::mergeTags($tags_page_1, ['rendered']));
+    $this->assertTrue(strpos($output['#markup'], $random_name) !== FALSE);
+  }
+
+  /**
+   * Tests cache tags on output & result cache items for an entity-based view.
+   */
+  public function testEntityRenderBasedView() {
+    $view = Views::getview('entity_test_row');
+    $view->getDisplay()->overrideOption('cache', [
+      'type' => 'tag',
+    ]);
+
+    // Empty result (no entities yet).
+    $base_tags = ['config:views.view.entity_test_row', 'entity_test_list'];
+    $base_render_tags = Cache::mergeTags($base_tags, ['rendered']);
+    $this->assertViewsCacheTags($view, $base_tags, $base_render_tags);
+
+    // Non-empty result (1 entity).
+    $entities[] = $entity = EntityTest::create();
+    $entity->save();
+
+    $base_render_tags = Cache::mergeTags($base_render_tags, ['entity_test_view']);
+    $result_tags_with_entity = Cache::mergeTags($base_tags, $entities[0]->getCacheTags());
+    $render_tags_with_entity = Cache::mergeTags($base_render_tags, $entities[0]->getCacheTags());
+    $this->assertViewsCacheTags($view, $result_tags_with_entity, $render_tags_with_entity);
+
+
+    // Paged result (more entities than the items-per-page limit).
+    for ($i = 0; $i < 5; $i++) {
+      $entities[] = $entity = EntityTest::create();
+      $entity->save();
+    }
+
+    $new_entities_cache_tags = Cache::mergeTags($entities[1]->getCacheTags(), $entities[2]->getCacheTags(), $entities[3]->getCacheTags(), $entities[4]->getCacheTags(), $entities[5]->getCacheTags());
+    $result_tags_page_1 = Cache::mergeTags($base_tags, $new_entities_cache_tags);
+    $render_tags_page_1 = Cache::mergeTags($base_render_tags, $new_entities_cache_tags);
+    $this->assertViewsCacheTags($view, $result_tags_page_1, $render_tags_page_1);
+  }
+
+}
diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php
index d74fe31..d927e73 100644
--- a/core/modules/views/src/ViewExecutable.php
+++ b/core/modules/views/src/ViewExecutable.php
@@ -8,6 +8,7 @@
 namespace Drupal\views;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Routing\RouteProviderInterface;
@@ -1398,6 +1399,7 @@ public function render($display_id = NULL) {
       }
 
       $this->display_handler->output = $this->display_handler->render();
+
       if ($cache) {
         $cache->cacheSet('output');
       }
@@ -1424,6 +1426,22 @@ public function render($display_id = NULL) {
   }
 
   /**
+   * Gets the cache tags associated with the executed view.
+   *
+   * Note: The cache plugin controls the used tags, so you can override it, if
+   *   needed.
+   *
+   * @return string[]
+   *   An array of cache tags.
+   */
+  public function getCacheTags() {
+    $this->initDisplay();
+    /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
+    $cache = $this->display_handler->getPlugin('cache');
+    return $cache->getCacheTags();
+  }
+
+  /**
    * Builds the render array outline for the given display.
    *
    * This render array has a #pre_render callback which will call
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml
new file mode 100644
index 0000000..8966bb8
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml
@@ -0,0 +1,74 @@
+langcode: und
+status: true
+dependencies: {  }
+id: entity_test_fields
+label: ''
+module: views
+description: ''
+tag: ''
+base_table: entity_test
+base_field: nid
+core: '8'
+display:
+  default:
+    display_options:
+      access:
+        type: none
+      cache:
+        type: none
+      exposed_form:
+        type: basic
+      fields:
+        id:
+          alter:
+            alter_text: false
+          element_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          plugin_id: numeric
+          entity_type: entity_test
+          entity_field: id
+          id: id
+          table: entity_test
+          field: id
+        name:
+          alter:
+            alter_text: false
+            ellipsis: true
+            html: false
+            make_link: false
+            strip_tags: false
+            trim: false
+            word_boundary: true
+          empty_zero: false
+          field: name
+          hide_empty: false
+          id: name
+          table: entity_test
+          plugin_id: standard
+          entity_type: entity_test
+          entity_field: name
+      sorts:
+        id:
+          table: entity_test
+          id: id
+          field: id
+          plugin_id: standard
+          entity_type: entity_test
+          entity_field: id
+          order: desc
+      pager:
+        type: full
+        options:
+          items_per_page: 5
+      style:
+        type: default
+      row:
+        type: fields
+    display_plugin: default
+    display_title: Master
+    id: default
+    position: 0
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml
new file mode 100644
index 0000000..2384fe0
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml
@@ -0,0 +1,41 @@
+langcode: und
+status: true
+dependencies: {  }
+id: entity_test_row
+label: ''
+module: views
+description: ''
+tag: ''
+base_table: entity_test
+base_field: nid
+core: '8'
+display:
+  default:
+    display_options:
+      access:
+        type: none
+      cache:
+        type: none
+      exposed_form:
+        type: basic
+      sorts:
+        id:
+          table: entity_test
+          id: id
+          field: id
+          plugin_id: standard
+          entity_type: entity_test
+          entity_field: id
+          order: desc
+      pager:
+        type: full
+        options:
+          items_per_page: 5
+      style:
+        type: default
+      row:
+        type: 'entity:entity_test'
+    display_plugin: default
+    display_title: Master
+    id: default
+    position: 0
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml
index 82908d8..6dd5fc5 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml
@@ -42,3 +42,15 @@ display:
           table: views_test_data
           field: id
           relationship: none
+
+  page_1:
+    display_plugin: page
+    id: page_1
+    display_options:
+      defaults:
+        pager: false
+      pager:
+        type: full
+        options:
+          items_per_page: 2
+
