Skip to content

Commit

Permalink
implemented automated browser tests
Browse files Browse the repository at this point in the history
  • Loading branch information
liabru committed Aug 3, 2015
1 parent 6a88256 commit a88b3ba
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 218 deletions.
14 changes: 13 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ module.exports = function(grunt) {
options: {
jshintrc: '.jshintrc'
},
all: ['src/**/*.js', 'demo/js/*.js', '!src/module/*']
all: ['src/**/*.js', 'demo/js/*.js', 'tests/browser/TestDemo.js', '!src/module/*']
},
connect: {
watch: {
Expand Down Expand Up @@ -107,6 +107,16 @@ module.exports = function(grunt) {
src: 'build/<%= buildName %>.js',
dest: 'build/<%= buildName %>.js'
}
},
shell: {
testBrowser: {
command: 'phantomjs tests/browser/TestDemo.js',
options: {
execOptions: {
timeout: 1000 * 60
}
}
}
}
});

Expand All @@ -118,9 +128,11 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-yuidoc');
grunt.loadNpmTasks('grunt-preprocess');
grunt.loadNpmTasks('grunt-shell');

grunt.registerTask('default', ['test', 'build']);
grunt.registerTask('test', ['jshint']);
grunt.registerTask('testBrowser', ['shell:testBrowser']);
grunt.registerTask('dev', ['build:dev', 'connect:watch', 'watch']);

grunt.registerTask('build', function(mode) {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"rigid body physics"
],
"devDependencies": {
"fast-json-patch": "^0.5.4",
"grunt": "~0.4.2",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-connect": "~0.6.0",
Expand All @@ -28,7 +29,8 @@
"grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-watch": "~0.5.3",
"grunt-contrib-yuidoc": "~0.5.1",
"grunt-preprocess": "^4.1.0"
"grunt-preprocess": "^4.1.0",
"grunt-shell": "^1.1.2"
},
"scripts": {
"dev": "npm install && grunt dev",
Expand Down
249 changes: 131 additions & 118 deletions tests/browser/TestDemo.js
Original file line number Diff line number Diff line change
@@ -1,118 +1,52 @@
var page = require('webpage').create();
var fs = require('fs');
var Resurrect = require('./lib/resurrect');
var _ = require('./lib/lodash');

page.onConsoleMessage = function(msg) {
console.log(msg);
};

page.onError = function(msg) {
console.log(msg);
};

phantom.onError = function(msg, trace) {
var msgStack = ['PHANTOM ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : ''));
});
}
console.error(msgStack.join('\n'));
phantom.exit(1);
};

var log = function(msg) {
console.log(JSON.stringify(msg));
}

var type = function(obj) {
// https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
if (obj === global) {
return "global";
}
return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
};

var compare = function(objectA, objectB) {
if (objectA === objectB) {
return { equal: true };
}

if ((type(objectA) === 'undefined' && type(objectB) === 'null')
|| (type(objectA) === 'null' && type(objectB) === 'undefined')) {
return { equal: true };
}

if (type(objectA) !== type(objectB)) {
return { equal: false, expected: type(objectA), actual: type(objectB) };
}

if (_.isNumber(objectA)) {
if (objectA.toFixed(5) === objectB.toFixed(5)) {
return { equal: true };
} else {
return { equal: false, expected: objectA, actual: objectB };
}
}

if (_.isArray(objectA)) {
var arrayDelta = [],
isEqual = true;

for (var i = 0; i < Math.max(objectA.length, objectB.length); i++) {
var diff = compare(objectA[i], objectB[i]);
arrayDelta[i] = diff;

if (diff.equal !== true) {
isEqual = false;
}
}

return isEqual ? { equal: true } : arrayDelta;
}

if (_.isObject(objectA)) {
var keys = _.union(_.keys(objectA), _.keys(objectB)),
objectDelta = { equal: true };

for (var i = 0; i < keys.length; i++) {
var key = keys[i],
diff = compare(objectA[key], objectB[key]);

if (diff.equal !== true) {
objectDelta[key] = diff;
objectDelta.equal = false;
}
}

return objectDelta.equal ? { equal: true } : objectDelta;
var compare = require('fast-json-patch').compare;
var system = require('system');

var demo,
frames = 10,
testUrl = 'http://localhost:9000/demo/dev.html',
refsPath = 'tests/browser/refs',
diffsPath = 'tests/browser/diffs';

var update = arg('--update'),
updateAll = typeof arg('--updateAll') !== 'undefined',
diff = arg('--diff');

var resurrect = new Resurrect({ cleanup: true }),
created = [],
changed = [];

var test = function(status) {
if (status === 'fail') {
console.log('failed to load', testUrl);
console.log('check dev server is running!');
console.log('use `grunt dev`');
phantom.exit(1);
return;
}

return { equal: false, expected: objectA, actual: objectB };
};

page.open('http://localhost:9000/demo/dev.html', function(status) {
var demos = page.evaluate(function() {
var options = Array.prototype.slice.call(document.getElementById('demo-select').options);
return options.map(function(o) { return o.value });
var demoSelect = document.getElementById('demo-select'),
options = Array.prototype.slice.call(demoSelect);
return options.map(function(o) { return o.value; });
});

var worldsPath = 'tests/browser/worlds',
diffsPath = 'tests/browser/diffs'
resurrect = new Resurrect({ cleanup: true }),
frames = 10;

fs.removeTree(diffsPath);
fs.makeDirectory(diffsPath);

console.log(demos);
if (diff) {
fs.makeDirectory(diffsPath);
}

for (var i = 0; i < demos.length; i += 1) {
var demo = demos[i],
worldStartPath = worldsPath + '/' + demo + '/' + demo + '-0.json',
worldEndPath = worldsPath + '/' + demo + '/' + demo + '-' + frames + '.json',
demo = demos[i];

var hasChanged = false,
hasCreated = false,
forceUpdate = update === demo || updateAll,
worldStartPath = refsPath + '/' + demo + '/' + demo + '-0.json',
worldEndPath = refsPath + '/' + demo + '/' + demo + '-' + frames + '.json',
worldStartDiffPath = diffsPath + '/' + demo + '/' + demo + '-0.json',
worldEndDiffPath = diffsPath + '/' + demo + '/' + demo + '-' + frames + '.json';

Expand All @@ -125,44 +59,123 @@ page.open('http://localhost:9000/demo/dev.html', function(status) {

var worldEnd = page.evaluate(function(demo, frames) {
var engine = Matter.Demo._engine;

for (var j = 0; j <= frames; j += 1) {
Matter.Events.trigger(engine, 'tick', { timestamp: engine.timing.timestamp });
Matter.Engine.update(engine, engine.timing.delta);
Matter.Events.trigger(engine, 'afterTick', { timestamp: engine.timing.timestamp });
}

return engine.world;
}, demo, frames);

if (fs.exists(worldStartPath)) {
var worldStartRef = resurrect.resurrect(fs.read(worldStartPath));
var worldStartDiff = compare(worldStart, worldStartRef);

if (!worldStartDiff.equal) {
fs.write(worldStartDiffPath, JSON.stringify(worldStartDiff, null, 2), 'w');
console.log(demo, 'start equal:', worldStartDiff.equal);
if (worldStartDiff.length !== 0) {
if (diff) {
fs.write(worldStartDiffPath, JSON.stringify(worldStartDiff, null, 2), 'w');
}

if (forceUpdate) {
hasCreated = true;
fs.write(worldStartPath, resurrect.stringify(worldStart, null, 2), 'w');
} else {
hasChanged = true;
}
}
} else {
console.warn('no existing start reference world for', demo);
fs.write(worldStartPath, resurrect.stringify(worldStart), 'w');
console.log('wrote', worldEndPath);
hasCreated = true;
fs.write(worldStartPath, resurrect.stringify(worldStart, null, 2), 'w');
}

if (fs.exists(worldEndPath)) {
var worldEndRef = resurrect.resurrect(fs.read(worldEndPath));
var worldEndDiff = compare(worldEnd, worldEndRef);

if (!worldEndDiff.equal) {
fs.write(worldEndDiffPath, JSON.stringify(worldEndDiff, null, 2), 'w');
console.log(demo, 'end equal:', worldEndDiff.equal);
if (worldEndDiff.length !== 0) {
if (diff) {
fs.write(worldEndDiffPath, JSON.stringify(worldEndDiff, null, 2), 'w');
}

if (forceUpdate) {
hasCreated = true;
fs.write(worldEndPath, resurrect.stringify(worldEnd, null, 2), 'w');
} else {
hasChanged = true;
}
}
} else {
console.warn('no existing end reference world for', demo);
fs.write(worldEndPath, resurrect.stringify(worldEnd), 'w');
console.log('wrote', worldEndPath);
hasCreated = true;
fs.write(worldEndPath, resurrect.stringify(worldEnd, null, 2), 'w');
}

if (hasChanged) {
changed.push("'" + demo + "'");
system.stdout.write('x');
} else if (hasCreated) {
created.push("'" + demo + "'");
system.stdout.write('+');
} else {
system.stdout.write('.');
}
}

if (created.length > 0) {
console.log('\nupdated', created.join(', '));
}

var isOk = changed.length === 0 ? 1 : 0;

console.log('');

if (isOk) {
console.log('ok');
} else {
console.log('changes detected on:');
console.log(changed.join(', '));
console.log('review, then --update [name] or --updateAll');
console.log('use --diff for diff log');
}

phantom.exit(!isOk);
};

function arg(name) {
var index = system.args.indexOf(name);
if (index >= 0) {
return system.args[index + 1] || true;
}
return undefined;
}

page.onError = function(msg, trace) {
setTimeout(function() {
var msgStack = ['testing \'' + demo + '\'', msg];

if (trace && trace.length) {
trace.forEach(function(t) {
msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (fn: ' + t.function +')' : ''));
});
}

console.log(msgStack.join('\n'));
phantom.exit(1);
}, 0);
};


page.onResourceReceived = function(res) {
setTimeout(function() {
if (res.stage === 'end'
&& (res.status !== 304 && res.status !== 200 && res.status !== null)) {
console.log('error', res.status, res.url);
phantom.exit(1);
}
}, 0);
};

console.log('done');
phantom.onError = page.onError;

phantom.exit();
});
page.open(testUrl, test);
Loading

0 comments on commit a88b3ba

Please sign in to comment.