diff --git a/gulp/tasks/test.js b/gulp/tasks/test.js
index 1c1934ae..0eea16da 100644
--- a/gulp/tasks/test.js
+++ b/gulp/tasks/test.js
@@ -13,16 +13,7 @@
 var gulp = require('gulp'),
     gulp_qunit = require('gulp-qunit'),
     node_qunit = require('qunit'),
-    gutil = require('gulp-util'),
-    extend = require('extend'),
-    minimist = require('minimist');
-
-// Support simple command line options to pass on to test:node, to display
-// errors selectively, e.g.:
-// gulp test:node --assertions
-var options = minimist(process.argv.slice(2), {
-  boolean: true
-});
+    gutil = require('gulp-util');
 
 gulp.task('test', ['test:browser']);
 
@@ -32,12 +23,11 @@ gulp.task('test:browser', ['minify:acorn'], function() {
 });
 
 gulp.task('test:node', ['minify:acorn'], function(callback) {
-    var name = 'node-qunit';
-    node_qunit.setup({
-        log: extend({ errors: true }, options)
-    });
     // Use the correct working directory for tests:
     process.chdir('./test');
+    // Deactivate all logging since we're doing our own directly to gutil.log()
+    // from helpers.js
+    node_qunit.setup({ log: {} });
     node_qunit.run({
         maxBlockDuration: 100 * 1000,
         deps: [
@@ -55,24 +45,7 @@ gulp.task('test:node', ['minify:acorn'], function(callback) {
         // for the loading, which was requested above.
         code: 'load.js'
     }, function(err, stats) {
-        var result;
-        if (err) {
-            result = new gutil.PluginError(name, err);
-        } else {
-            // Imitate the way gulp-qunit formats results and errors.
-            var color = gutil.colors[stats.failed > 0 ? 'red' : 'green'];
-            gutil.log('Took ' + stats.runtime + ' ms to run ' +
-                gutil.colors.blue(stats.assertions) + ' tests. ' +
-                color(stats.passed + ' passed, ' + stats.failed + ' failed.'));
-            if (stats.failed > 0) {
-                err = 'QUnit assertions failed';
-                gutil.log(name + ': ' + gutil.colors.red('✖ ') + err);
-                result = new gutil.PluginError(name, err);
-            } else {
-                gutil.log(name + ': ' + gutil.colors.green('✔ ') +
-                    'QUnit assertions all passed');
-            }
-        }
-        callback(result);
+        err = err || stats.failed > 0 && 'QUnit assertions failed';
+        callback(err && new gutil.PluginError('node-qunit', err));
     });
 });
diff --git a/package.json b/package.json
index 03728cf4..5139791e 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
     "gulp": "^3.9.0",
     "gulp-cached": "^1.1.0",
     "gulp-jshint": "^2.0.0",
-    "gulp-prepro": "^2.1.0",
+    "gulp-prepro": "^2.2.0",
     "gulp-qunit": "git://github.com/lehni/gulp-qunit.git#459c5603ceac460327a40dc89df6f19c786dc61b",
     "gulp-rename": "^1.2.2",
     "gulp-rimraf": "^0.2.0",
@@ -56,8 +56,7 @@
     "jshint": "2.8.x",
     "jshint-summary": "^0.4.0",
     "merge-stream": "^1.0.0",
-    "minimist": "^1.2.0",
-    "prepro": "^2.1.0",
+    "prepro": "^2.2.0",
     "qunit": "^0.7.7",
     "qunitjs": "^1.20.0",
     "require-dir": "^0.3.0",
diff --git a/test/helpers.js b/test/helpers.js
index 446808be..9ae47ae8 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -15,8 +15,52 @@ var isNode = typeof global === 'object',
 
 if (isNode) {
     root = global;
-    // Resemble.js needs the Image constructor this global.
+    // Resemble.js needs the Image constructor global.
     global.Image = paper.window.Image;
+    // Handle logging to gulp directly from here, imitating the way gulp-qunit
+    // logs and formats results and errors:
+    var gutil = require('gulp-util'),
+        colors = gutil.colors,
+        done = false;
+    QUnit.log(function(details) {
+        if (!details.result) {
+            var lines = [
+                colors.red('Test failed') + ': ' + details.module + ': '
+                        + details.name
+            ];
+            var line = 'Failed assertion: ' + (details.message || '');
+            if (details.expected !== undefined) {
+                line += ', expected: ' + details.expected + ', but was: '
+                        + details.actual;
+            }
+            lines.push(line);
+            if (details.source) {
+                lines = lines.concat(details.source.split(/\r\n|\n|\r/mg));
+            }
+            lines.forEach(function(line) {
+                gutil.log(line);
+            });
+        } else if (false) {
+            gutil.log(colors.green('Test succeeded') + ': ' + details.module
+                    + ': ' + details.name +': '  + (details.message || ''));
+        }
+    });
+    QUnit.done(function(details) {
+        if (done)
+            return;
+        var color = colors[details.failed > 0 ? 'red' : 'green'];
+        gutil.log('Took ' + details.runtime + 'ms to run '
+            + colors.blue(details.total) + ' tests. ' + color(details.passed
+                + ' passed, ' + details.failed + ' failed.'));
+        if (details.failed > 0) {
+            gutil.log('node-qunit: ' + gutil.colors.red('✖')
+                + ' QUnit assertions failed');
+        } else {
+            gutil.log('node-qunit: ' + gutil.colors.green('✔')
+                + ' QUnit assertions all passed');
+        }
+        done = true;
+    });
 } else {
     root = window;
     // This is only required when running in the browser:
@@ -26,20 +70,10 @@ if (isNode) {
     // TODO: Ideally we should fix this in QUnit instead.
     delete window.history;
     window.history = {};
-
     QUnit.begin(function() {
         if (QUnit.urlParams.hidepassed) {
             document.getElementById('qunit-tests').className += ' hidepass';
         }
-        resemble.outputSettings({
-            errorColor: {
-                red: 255,
-                green: 51,
-                blue: 0
-            },
-            errorType: 'flat',
-            transparency: 1
-        });
     });
 }
 
@@ -47,50 +81,50 @@ if (isNode) {
 if (!('Base' in root))
     paper.install(root);
 
+// Override console.error, so that we can catch errors that are only logged to
+// the console.
 var errorHandler = console.error;
 console.error = function() {
     QUnit.pushFailure([].join.call(arguments, ' '), QUnit.config.current.stack);
     errorHandler.apply(this, arguments);
 };
 
+QUnit.done(function(details) {
+    console.error = errorHandler;
+});
+
+var currentProject,
+    // In case we're stuck with an old QUnit, use a fake assert object with just
+    // the functions that we need:
+    // For now, a async() function returning a done() function:
+    fakeAssert = {
+        async: function() {
+            return function() {
+                QUnit.start();
+            };
+        }
+    };
+
 // NOTE: In order to "export" all methods into the shared Prepro.js scope when
 // using node-qunit, we need to define global functions as:
 // `var name = function() {}`. `function name() {}` does not work!
 
-var currentProject;
-
 var test = function(testName, expected) {
-    var parameters = expected.toString().match(/^\s*function[^\(]*\(([^\)]*)/)[1];
     // If this is running on an older version of QUnit (e.g. node-qunit is stuck
     // with v1.10 for now), emulate the new assert.async() syntax through
     // QUnit.asyncTest() and QUnit.start();
-    if (!QUnit.async && parameters === 'assert') {
-        return QUnit.asyncTest(testName, function() {
-            // Since tests may be asynchronous, remove the old project before
-            // running the next test.
-            if (currentProject)
-                currentProject.remove();
-            currentProject = new Project();
-            // Pass a fake assert object with just the functions that we need,
-            // so far a async() function returning a done() function:
-            expected({
-                async: function() {
-                    return function() {
-                        QUnit.start();
-                    };
-                }
-            });
-        });
-    } else {
-        return QUnit.test(testName, function(assert) {
-            // Since tests may be asynchronous, remove the old project before
-            // running the next test.
-            if (currentProject)
-                currentProject.remove();
-            currentProject = new Project();
-            expected(assert);
-        });
-    }
+    var emulate = !QUnit.async && 'assert' ===
+            // Get the parameter list from the passed function to see if we're
+            // expecting the assert object to do async...
+            expected.toString().match(/^\s*function[^\(]*\(([^\)]*)/)[1];
+    return QUnit[emulate ? 'asyncTest' : 'test'](testName, function(assert) {
+        // Since tests can be asynchronous, remove the old project before
+        // running the next test.
+        if (currentProject)
+            currentProject.remove();
+        currentProject = new Project();
+        expected(emulate ? fakeAssert : assert);
+    });
 };
 
 // Override equals to convert functions to message and execute them as tests()
@@ -140,33 +174,6 @@ var identicalAfterCloning = {
     Symbol: true
 };
 
-var getFunctionMessage = function(func) {
-    var message = func.toString().match(
-        /^\s*function[^\{]*\{([\s\S]*)\}\s*$/)[1]
-            .replace(/    /g, '')
-            .replace(/^\s+|\s+$/g, '');
-    if (/^return /.test(message)) {
-        message = message
-            .replace(/^return /, '')
-            .replace(/;$/, '');
-    }
-    return message;
-};
-
-var createSVG = function(str, attrs) {
-    if (attrs) {
-        // Similar to SVGExport's createElement / setAttributes.
-        var node = document.createElementNS('http://www.w3.org/2000/svg', str);
-        for (var key in attrs)
-            node.setAttribute(key, attrs[key]);
-        return node;
-    } else {
-        return new window.DOMParser().parseFromString(
-            '<svg xmlns="http://www.w3.org/2000/svg">' + str + '</svg>',
-            'text/xml');
-    }
-};
-
 // Register a jsDump parser for Base.
 QUnit.jsDump.setParser('Base', function (obj, stack) {
     // Just compare the string representation of classes inheriting from Base,
@@ -191,8 +198,7 @@ var compareProperties = function(actual, expected, properties, message, options)
     }
 };
 
-var compareItem = function(actual, expected, message, options, properties) {
-    options = options || {};
+var compareRasterized = function(actual, expected, message, options) {
 
     function rasterize(item, group, resolution) {
         var raster = null;
@@ -209,92 +215,104 @@ var compareItem = function(actual, expected, message, options, properties) {
                 + '" src="' + raster.source + '">';
     }
 
-    if (options.rasterize) {
-        // In order to properly compare pixel by pixel, we need to put each item
-        // into a group with a white background of the united dimensions of the
-        // bounds of both items before rasterizing.
-        var resolution = options.rasterize === true ? 72 : options.rasterize,
-            actualBounds = actual.strokeBounds,
-            expecedBounds = expected.strokeBounds,
-            bounds = actualBounds.isEmpty()
-                    ? expecedBounds
-                    : expecedBounds.isEmpty()
-                    ? actualBounds
-                    : actualBounds.unite(expecedBounds);
-        if (bounds.isEmpty()) {
-            QUnit.push(true, 'empty', 'empty', message);
-            return;
-        }
-        var group = actual && expected && new Group({
-                insert: false,
-                children: [
-                    new Shape.Rectangle({
-                        rectangle: bounds,
-                        fillColor: 'white'
-                    })
-                ]
-            }),
-            actual = rasterize(actual, group, resolution),
-            expected = rasterize(expected, group, resolution);
-        if (!actual || !expected) {
-            QUnit.pushFailure('Unable to compare rasterized items: ' +
-                    (!actual ? 'actual' : 'expected') + ' item is null',
-                    QUnit.stack(2));
-        } else {
-            // Use resemble.js to compare the two rasterized items.
-            var id = QUnit.config.current.testId,
-                index = QUnit.config.current.assertions.length + 1,
-                result;
-            resemble(actual.getImageData())
-                .compareTo(expected.getImageData())
-                // When working with imageData, this call is synchronous:
-                .onComplete(function(data) { result = data; });
-            var tolerance = (options.tolerance || 1e-4) * 100, // percentages...
-                fixed = ((1 / tolerance) + '').length - 1,
-                identical = result ? 100 - result.misMatchPercentage : 0,
-                reached = identical.toFixed(fixed),
-                hundred = (100).toFixed(fixed),
-                ok = reached == hundred;
-            QUnit.push(ok, reached + '% identical', hundred + '% identical',
-                    message);
-            if (!ok && result && !isNode) {
-                // Get the right entry for this unit test and assertion, and
-                // replace the results with images
-                var entry = document.getElementById('qunit-test-output-' + id)
-                        .querySelector('li:nth-child(' + (index) + ')'),
-                    bounds = result.diffBounds;
-                entry.querySelector('.test-expected td').innerHTML =
-                        getImageTag(expected);
-                entry.querySelector('.test-actual td').innerHTML =
-                        getImageTag(actual);
-                entry.querySelector('.test-diff td').innerHTML = '<pre>' + text
-                        + '</pre><br>'
-                        + '<img src="' + result.getImageDataUrl() + '">';
-            }
-        }
-    } else {
-        if (options.cloned)
-            QUnit.notStrictEqual(actual.id, expected.id,
-                    'not ' + message + '.id');
-        QUnit.strictEqual(actual.constructor, expected.constructor,
-                message + '.constructor');
-        // When item is cloned and has a name, the name will be versioned:
-        equals(actual.name,
-                options.cloned && expected.name
-                    ? expected.name + ' 1' : expected.name,
-                message + '.name');
-        compareProperties(actual, expected, ['children', 'bounds', 'position',
-                'matrix', 'data', 'opacity', 'locked', 'visible', 'blendMode',
-                'selected', 'fullySelected', 'clipMask', 'guide'],
-                message, options);
-        if (properties)
-            compareProperties(actual, expected, properties, message, options);
-        // Style
-        compareProperties(actual.style, expected.style, ['fillColor',
-                'strokeColor', 'strokeCap', 'strokeJoin', 'dashArray',
-                'dashOffset', 'miterLimit', 'fontSize', 'font', 'leading',
-                'justification'], message + '.style', options);
+    // In order to properly compare pixel by pixel, we need to put each item
+    // into a group with a white background of the united dimensions of the
+    // bounds of both items before rasterizing.
+    var resolution = options.rasterize === true ? 72 : options.rasterize,
+        actualBounds = actual.strokeBounds,
+        expecedBounds = expected.strokeBounds,
+        bounds = actualBounds.isEmpty()
+                ? expecedBounds
+                : expecedBounds.isEmpty()
+                ? actualBounds
+                : actualBounds.unite(expecedBounds);
+    if (bounds.isEmpty()) {
+        QUnit.push(true, 'empty', 'empty', message);
+        return;
     }
+    var group = actual && expected && new Group({
+            insert: false,
+            children: [
+                new Shape.Rectangle({
+                    rectangle: bounds,
+                    fillColor: 'white'
+                })
+            ]
+        }),
+        actual = rasterize(actual, group, resolution),
+        expected = rasterize(expected, group, resolution);
+    if (!actual || !expected) {
+        QUnit.pushFailure('Unable to compare rasterized items: ' +
+                (!actual ? 'actual' : 'expected') + ' item is null',
+                QUnit.stack(2));
+    } else {
+        // Use resemble.js to compare the two rasterized items.
+        var id = QUnit.config.current.testId,
+            index = QUnit.config.current.assertions.length + 1,
+            result;
+        if (!resemble._setup) {
+            resemble._setup = true;
+            resemble.outputSettings({
+                errorColor: { red: 255, green: 51, blue: 0 },
+                errorType: 'flat',
+                transparency: 1
+            });
+        }
+        resemble(actual.getImageData())
+            .compareTo(expected.getImageData())
+            // When working with imageData, this call is synchronous:
+            .onComplete(function(data) { result = data; });
+        var tolerance = (options.tolerance || 1e-4) * 100, // percentages...
+            fixed = ((1 / tolerance) + '').length - 1,
+            identical = result ? 100 - result.misMatchPercentage : 0,
+            reached = identical.toFixed(fixed),
+            hundred = (100).toFixed(fixed),
+            ok = reached == hundred;
+        QUnit.push(ok, reached + '% identical', hundred + '% identical',
+                message);
+        if (!ok && result && !isNode) {
+            // Get the right entry for this unit test and assertion, and
+            // replace the results with images
+            var entry = document.getElementById('qunit-test-output-' + id)
+                    .querySelector('li:nth-child(' + (index) + ')'),
+                bounds = result.diffBounds;
+            entry.querySelector('.test-expected td').innerHTML =
+                    getImageTag(expected);
+            entry.querySelector('.test-actual td').innerHTML =
+                    getImageTag(actual);
+            entry.querySelector('.test-diff td').innerHTML = '<pre>' + text
+                    + '</pre><br>'
+                    + '<img src="' + result.getImageDataUrl() + '">';
+        }
+    }
+};
+
+var compareItem = function(actual, expected, message, options, properties) {
+    options = options || {};
+    if (options.rasterize) {
+        return compareRasterized(actual, expected, message, options);
+    }
+    if (options.cloned)
+        QUnit.notStrictEqual(actual.id, expected.id,
+                'not ' + message + '.id');
+    QUnit.strictEqual(actual.constructor, expected.constructor,
+            message + '.constructor');
+    // When item is cloned and has a name, the name will be versioned:
+    equals(actual.name,
+            options.cloned && expected.name
+                ? expected.name + ' 1' : expected.name,
+            message + '.name');
+    compareProperties(actual, expected, ['children', 'bounds', 'position',
+            'matrix', 'data', 'opacity', 'locked', 'visible', 'blendMode',
+            'selected', 'fullySelected', 'clipMask', 'guide'],
+            message, options);
+    if (properties)
+        compareProperties(actual, expected, properties, message, options);
+    // Style
+    compareProperties(actual.style, expected.style, ['fillColor',
+            'strokeColor', 'strokeCap', 'strokeJoin', 'dashArray',
+            'dashOffset', 'miterLimit', 'fontSize', 'font', 'leading',
+            'justification'], message + '.style', options);
 };
 
 // A list of comparator functions, based on `expected` type. See equals() for
@@ -453,3 +471,33 @@ var comparators = {
                 message, options);
     }
 };
+
+// Some other helpers:
+
+var getFunctionMessage = function(func) {
+    var message = func.toString().match(
+        /^\s*function[^\{]*\{([\s\S]*)\}\s*$/)[1]
+            .replace(/    /g, '')
+            .replace(/^\s+|\s+$/g, '');
+    if (/^return /.test(message)) {
+        message = message
+            .replace(/^return /, '')
+            .replace(/;$/, '');
+    }
+    return message;
+};
+
+var createSVG = function(str, attrs) {
+    if (attrs) {
+        // Similar to SVGExport's createElement / setAttributes.
+        var node = document.createElementNS('http://www.w3.org/2000/svg', str);
+        for (var key in attrs)
+            node.setAttribute(key, attrs[key]);
+        return node;
+    } else {
+        return new window.DOMParser().parseFromString(
+            '<svg xmlns="http://www.w3.org/2000/svg">' + str + '</svg>',
+            'text/xml');
+    }
+};
+
diff --git a/test/load.js b/test/load.js
index 2a1f3379..ed1cf72e 100644
--- a/test/load.js
+++ b/test/load.js
@@ -11,5 +11,7 @@
  */
 
 /*#*/ include('helpers.js');
+// We need to load resemble.js after helpers.js, since for Node, helpers makes
+// sure window, document and Image are made global first.
 /*#*/ include('../node_modules/resemblejs/resemble.js', { namespace: 'resemble' });
 /*#*/ include('tests/load.js');
diff --git a/test/tests/SVGImport.js b/test/tests/SVGImport.js
index 90f6a5d7..baf74773 100644
--- a/test/tests/SVGImport.js
+++ b/test/tests/SVGImport.js
@@ -115,8 +115,7 @@ test('Import SVG polyline', function() {
 });
 
 test('Import complex CompoundPath and clone', function() {
-    var svg = createSVG('<path fill="red" d="M4,14h20v-2H4V14z M15,26h7v-2h-7V26z M15,22h9v-2h-9V22z M15,18h9v-2h-9V18z M4,26h9V16H4V26z M28,10V6H0v22c0,0,0,4,4,4 h25c0,0,3-0.062,3-4V10H28z M4,30c-2,0-2-2-2-2V8h24v20c0,0.921,0.284,1.558,0.676,2H4z"/>;');
-    var item = paper.project.importSVG(svg.firstChild);
+    var svg = createSVG('<path fill="red" d="M4,14h20v-2H4V14z M15,26h7v-2h-7V26z M15,22h9v-2h-9V22z M15,18h9v-2h-9V18z M4,26h9V16H4V26z M28,10V6H0v22c0,0,0,4,4,4 h25c0,0,3-0.062,3-4V10H28z M4,30c-2,0-2-2-2-2V8h24v20c0,0.921,0.284,1.558,0.676,2H4z"/>');
+    var item = paper.project.importSVG(svg);
     equals(item.clone(), item, null, { cloned: true });
-    return;
 });