diff --git a/.travis.yml b/.travis.yml
index a2d51377ec0059c13fab5ca4a7dd3cb385b95f25..5bc0f66ee5be09c898e5f5c6ad07e01dda251bf1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,6 +25,7 @@ sudo: false
 node_js:
   - 5
   - 6
+  - 7
 
 # Build visual tests on Node.js 7
 matrix:
diff --git a/Gulpfile.babel.js b/Gulpfile.babel.js
index d08d4285d0282b6686c7d374f62b114b078359fa..7351e922f8a79c18862650222c171cb06f8d7c59 100755
--- a/Gulpfile.babel.js
+++ b/Gulpfile.babel.js
@@ -311,6 +311,12 @@ gulp.task("tests:visual:update",
 gulp.task("tests:visual:clean",
   load("tests/visual/clean"))
 
+/*
+ * Open a SauceConnect session for manual testing
+ */
+gulp.task("tests:visual:session",
+  load("tests/visual/session"))
+
 /* ----------------------------------------------------------------------------
  * Interface
  * ------------------------------------------------------------------------- */
diff --git a/lib/tasks/tests/visual/session.js b/lib/tasks/tests/visual/session.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8dcf4e966cd0bc5f6921c107584a287f7c7d5cf
--- /dev/null
+++ b/lib/tasks/tests/visual/session.js
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import moniker from "moniker"
+import * as ecstatic from "~/lib/servers/ecstatic"
+import * as sauce from "~/lib/servers/sauce-connect"
+
+/* ----------------------------------------------------------------------------
+ * Task: run visual tests
+ * ------------------------------------------------------------------------- */
+
+export default (gulp, config) => {
+  return done => {
+
+    /* Start static file server */
+    new Promise(resolve => {
+      ecstatic.start(`${config.tests.visual}/data`, 8000, resolve)
+
+    /* Open SauceConnect tunnel */
+    }).then(() => {
+      return new Promise((resolve, reject) => {
+        if (!process.env.SAUCE_USERNAME ||
+            !process.env.SAUCE_ACCESS_KEY)
+          throw new Error(
+            "SauceConnect: please provide SAUCE_USERNAME " +
+            "and SAUCE_ACCESS_KEY")
+
+        /* Open tunnel */
+        sauce.start(
+          `Local #${moniker.choose()}`,
+          process.env.SAUCE_USERNAME,
+          process.env.SAUCE_ACCESS_KEY,
+          err => {
+            return err ? reject(err) : resolve(sauce)
+          })
+      })
+
+        /* Close tunnel on CTRL-C */
+        .then(() => {
+          return new Promise(resolve => {
+            process.on("SIGINT", () => {
+              sauce.stop(resolve)
+            })
+          })
+        })
+
+    /* Stop static file server */
+    })
+      .then(() => {
+        ecstatic.stop(done)
+      }, err => {
+        return done(err)
+      })
+  }
+}
diff --git a/package.json b/package.json
index fdf383924dab4402a7e1c2f2533b8b9523d3acff..bde65fb4ae111054cd93d9770927d7d1bdb56515 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,8 @@
     "lint": "scripts/lint",
     "start": "scripts/start",
     "test:visual:run": "scripts/test/visual/run",
-    "test:visual:update": "scripts/test/visual/update"
+    "test:visual:update": "scripts/test/visual/update",
+    "test:visual:session": "scripts/test/visual/session"
   },
   "dependencies": {},
   "devDependencies": {
diff --git a/scripts/test/visual/session b/scripts/test/visual/session
new file mode 100755
index 0000000000000000000000000000000000000000..61803861c8baf726ae5e86d341eb26c7bde7a399
--- /dev/null
+++ b/scripts/test/visual/session
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+# Check if "npm install" was executed
+if [[ ! -d `npm bin` ]]; then
+  echo "\"node_modules\" not found:"
+  echo "npm install"
+  exit 1
+fi
+
+# Run command
+`npm bin`/gulp tests:visual:session $@