Index: test/wrappers/pages.js
===================================================================
new file mode 100644
--- /dev/null
+++ b/test/wrappers/pages.js
@@ -0,0 +1,157 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const TEST_PAGES_URL = "https://testpages.adblockplus.org/";
+
+const assert = require("assert");
+const Jimp = require("jimp");
+const {By} = require("selenium-webdriver");
+
+function imageFromBase64(s)
+{
+  return Jimp.read(Buffer.from(s, "base64"));
+}
+
+function takeScreenshot(element)
+{
+  return element.takeScreenshot().then(
+    imageFromBase64,
+
+    // Chrome doesn't support taking screenshots of individual elements. So as
+    // a workaround, we scroll to the position of the element, take a screenshot
+    // of the viewport and crop it to the size and position of our element.
+    // This is not guaranteed to work on other browsers (mostly because
+    // the behavior of Driver.takeScreenshot() may vary across browsers).
+    () => element.getLocation().then(loc =>
+      element.getDriver().executeScript(`
+        window.scrollTo(${loc.x}, ${loc.y});
+        return [window.scrollX, window.scrollY];
+      `).then(result =>
+      {
+        let x = loc.x - result[0];
+        let y = loc.y - result[1];
+
+        return Promise.all([
+          element.getDriver().takeScreenshot().then(imageFromBase64),
+          element.getSize()
+        ]).then(([img, size]) =>
+          img.crop(x, y, size.width, size.height)
+        );
+      })
+    )
+  ).then(img => img.bitmap);
+}
+
+function getSections(driver)
+{
+  return driver.findElements(By.css("section")).then(elements =>
+    Promise.all(elements.map(e =>
+      Promise.all([
+        e.findElement(By.css("h2")).catch(() => null),
+        e.findElement(By.className("testcase-container")).catch(() => null),
+        e.findElements(By.css("pre"))
+      ])
+    ))
+  ).then(sections => sections.filter(
+    ([title, demo, filters]) => title && demo && filters.length > 0
+  ));
+}
+
+it("test pages", function()
+{
+  return this.driver.navigate().to(TEST_PAGES_URL).then(() =>
+    this.driver.findElements(By.css(".site-pagelist a"))
+  ).then(elements =>
+    Promise.all(elements.map(elem => elem.getAttribute("href")))
+  ).then(urls =>
+  {
+    let p1 = Promise.resolve();
+    for (let url of urls)
+      p1 = p1.then(() =>
+        this.driver.navigate().to(url)
+      ).then(() =>
+        Promise.all([
+          getSections(this.driver),
+          this.driver.findElement(By.css("h2")).getAttribute("textContent"),
+          this.driver.executeScript("document.body.classList.add('expected');")
+        ])
+      ).then(([sections, pageTitle]) =>
+        Promise.all(sections.map(([title, demo, filters]) =>
+          Promise.all([
+            title.getAttribute("textContent").then(testTitle =>
+              `${pageTitle.trim()} - ${testTitle.trim()}`
+            ),
+            takeScreenshot(demo),
+            Promise.all(filters.map(elem => elem.getAttribute("textContent")))
+          ])
+        ))
+      ).then(testCases =>
+      {
+        let p2 = Promise.resolve();
+        for (let i = 0; i < testCases.length; i++)
+        {
+          let [title, expectedScreenshot, filters] = testCases[i];
+          let platform = this.test.parent.title;
+
+          if (// https://issues.adblockplus.org/ticket/6917
+              title == "$subdocument - Test case" && platform == "gecko" ||
+              // Chromium doesn't support Flash
+              /^\$object(-subrequest)? /.test(title) && platform == "chrome")
+            continue;
+
+          p2 = p2.then(() =>
+            this.driver.navigate().to(this.origin + "/options.html")
+          ).then(() =>
+            this.driver.executeAsyncScript(`
+              let filters = arguments[0];
+              let callback = arguments[arguments.length - 1];
+              browser.runtime.sendMessage({type: "subscriptions.get",
+                                           downloadable: true,
+                                           special: true}).then(subs =>
+              {
+                for (let subscription of subs)
+                  browser.runtime.sendMessage({type: "subscriptions.remove",
+                                               url: subscription.url});
+                return browser.runtime.sendMessage({type: "filters.importRaw",
+                                                    text: filters});
+              }).then(() => callback(), callback);
+            `, filters.join("\n"))
+          ).then(error =>
+          {
+            if (error)
+              throw error;
+            return this.driver.navigate().to(url);
+          }).then(() =>
+            getSections(this.driver)
+          ).then(sections =>
+            takeScreenshot(sections[i][1])
+          ).then(screenshot =>
+            assert.ok(
+              screenshot.width == expectedScreenshot.width &&
+              screenshot.height == expectedScreenshot.height &&
+              screenshot.data.compare(expectedScreenshot.data) == 0,
+              title
+            )
+          );
+        }
+        return p2;
+      });
+    return p1;
+  });
+});
