New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

attester

Package Overview
Dependencies
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

attester - npm Package Compare versions

Comparing version 1.1.0 to 1.2.0

lib/middlewares/index.js

4

Gruntfile.js

@@ -19,3 +19,3 @@ /*

jshint: {
all : ['package.json', 'grunt.js', 'lib/**/*.js', 'spec/**/*.spec.js'],
all : ['package.json', 'grunt.js', 'lib/**/*.js', 'spec/**/*.spec.js', '!lib/**/html5shiv.js'],
options: {

@@ -52,2 +52,2 @@ eqnull: true,

};
};

@@ -20,3 +20,4 @@ /*

var parsedAgent = uaParser.parse(data.userAgent);
var ua = parsedAgent.userAgent;
var ua = parsedAgent.ua;
var osFamily = parsedAgent.os.family;
var res = {

@@ -27,4 +28,14 @@ displayName: parsedAgent.toString(),

minor: ua.minor,
patch: ua.patch
patch: ua.patch,
os: {
name: osFamily,
isDesktopLinux: /Linux/.test(data.userAgent.match) && (osFamily != "Android") && (osFamily != "webOS"),
isDesktopWindows: /Windows/.test(osFamily.match) && (osFamily != "Windows Phone")
}
};
// Regarding operating system, ua-parser is inconsistent, returns {family: "Windows 7"} but {family: "Windows
// Phone", major: "8"} etc. The only reliable part is the "family" string then.
// See https://github.com/tobie/ua-parser/blob/master/test_resources/test_user_agent_parser_os.yaml
if (res.family == "IE") {

@@ -57,6 +68,16 @@ // always something specific for IE!

if (match && config.minorVersion != null) {
match = config.majorVersion == browserInfo.minor;
match = config.minorVersion == browserInfo.minor;
}
if (match && config.os != null) {
// a little bit verbose to have readable code
match = false;
if (config.os == "Desktop Linux" && browserInfo.os.isDesktopLinux) {
match = true;
} else if (config.os == "Desktop Windows" && browserInfo.os.isDesktopWindows) {
match = true;
} else {
match = (config.os == browserInfo.os.name);
}
}
return match;
};

@@ -17,2 +17,3 @@ /*

var system = require('system');
var url;

@@ -19,0 +20,0 @@ var autoExit = false;

@@ -16,5 +16,8 @@ /*

var optimist = require('optimist');
var colors = require('colors');
var portfinder = require('portfinder');
var TestCampaign = require('./test-campaign/campaign.js');
var TestServer = require('./test-server/server.js');
var optimist = require('optimist');
var Logger = require('./logging/logger.js');

@@ -26,25 +29,28 @@ var ConsoleLogger = require('./logging/console-logger.js');

var JsonLogReport = require('./reports/json-log-report.js');
var colors = require('colors');
var writeReports = require('./reports/write-reports.js');
var childProcesses = require('./child-processes');
var childProcesses = require('./child-processes.js');
var exitProcess = require('./exit-process.js');
var config = require('./config');
var config = require('./config.js');
var optimizeParallel = require('./optimize-parallel.js');
var run = function () {
var opt = optimist.usage('Usage: $0 [options] [config.yml|config.json]').boolean(['flash-policy-server', 'json-console', 'help', 'server-only', 'version', 'colors', 'ignore-errors', 'ignore-failures']).string(['phantomjs-path']).describe({
'log-level': 'Level of logging: integer from 0 (no logging) to 4 (debug).',
'port': 'Port used for the web server. If set to 0, an available port is automatically selected.',
'browser': 'Path to any browser executable to execute the tests. Can be repeated multiple times.',
'colors': 'Uses colors (disable with --no-colors).',
'env': 'Environment configuration file. This file is available in the configuration object under env.',
'flash-policy-port': 'Port used for the built-in Flash policy server (needs --flash-policy-server). Can be 0 for a random port.',
'flash-policy-server': 'Whether to enable the built-in Flash policy server.',
'flash-policy-port': 'Port used for the built-in Flash policy server (needs --flash-policy-server). Can be 0 for a random port.',
'heartbeats': 'Delay (in ms) for heartbeats messages sent when --json-console is enabled. Use 0 to disable them.',
'help': 'Displays this help message and exits.',
'ignore-errors': 'When enabled, test errors (not including failures) will not cause this process to return a non-zero value.',
'ignore-failures': 'When enabled, test failures (anticipated errors) will not cause this process to return a non-zero value.',
'json-console': 'When enabled, JSON objects will be sent to stdout to provide information about tests.',
'heartbeats': 'Delay (in ms) for heartbeats messages sent when --json-console is enabled. Use 0 to disable them.',
'server-only': 'Only starts the web server, and configure it for the test campaign but do not start the campaign.',
'log-level': 'Level of logging: integer from 0 (no logging) to 4 (debug).',
'phantomjs-instances': 'Number of instances of PhantomJS to start.',
'phantomjs-path': 'Path to PhantomJS executable.',
'browser': 'Path to any browser executable to execute the tests. Can be repeated multiple times.',
'ignore-errors': 'When enabled, test errors (not including failures) will not cause this process to return a non-zero value.',
'ignore-failures': 'When enabled, test failures (anticipated errors) will not cause this process to return a non-zero value.',
'help': 'Displays this help message and exits.',
'colors': 'Uses colors (disable with --no-colors).',
'port': 'Port used for the web server. If set to 0, an available port is automatically selected.',
'server-only': 'Only starts the web server, and configure it for the test campaign but do not start the campaign.',
'slow-test-threshold': 'Threshold (in milliseconds) to mark long-running tests in the console report. Use 0 to disable.',
'task-timeout': 'Timeout of a task execution in milliseconds.',
'version': 'Displays the version number and exits.'

@@ -55,12 +61,14 @@ }).alias({

})['default']({
'colors': true,
'flash-policy-port': 0,
'flash-policy-server': false,
'heartbeats': 2000,
'port': 0,
'ignore-errors': false,
'ignore-failures': false,
'log-level': 3,
'colors': true,
'phantomjs-instances': process.env.npm_package_config_phantomjsInstances || 0,
'phantomjs-path': 'phantomjs',
'flash-policy-server': false,
'flash-policy-port': 0,
'ignore-errors': false,
'ignore-failures': false
'port': 7777,
'slow-test-threshold': 2500,
'task-timeout': 5 * 60 * 1000 // 5 minutes
});

@@ -106,3 +114,3 @@ var argv = opt.argv;

var configData = config.readConfig(argv._[0], argv.config, logger);
var configData = config.readConfig(argv._[0], argv.config, argv.env, logger);

@@ -113,3 +121,3 @@ var campaign = new TestCampaign(configData, logger);

reports.push(jsonReport);
reports.push(new ConsoleReport(logger));
reports.push(new ConsoleReport(logger, argv['slow-test-threshold']));
var logReports = configData['test-reports']['json-log-file'];

@@ -130,3 +138,7 @@ var i, l;

var phantomJSinstances = argv['phantomjs-instances'];
var suggestedInstances = argv['phantomjs-instances'];
var phantomJSinstances = optimizeParallel({
memoryPerInstance: 60,
maxInstances: suggestedInstances
}, logger);
if (phantomJSinstances > 0) {

@@ -137,2 +149,3 @@ campaign.on('result-serverAttached', function (event) {

var spawn = childProcesses.spawn;
// BACKWARD-COMPAT node 0.8 start
var checkPhantomjsSpawnExitCode = function (code) {

@@ -147,2 +160,10 @@ if (code === 127) {

};
// BACKWARD-COMPAT node 0.8 end
var onPhantomJsSpawnError = function (err) {
if (err.code === "ENOENT") {
logger.logError("Spawn: exited with code ENOENT. PhantomJS executable not found. Make sure to download PhantomJS and add its folder to your system's PATH, or pass the full path directly to Attester via --phantomjs-path.\nUsed command: '" + path + "'");
} else {
logger.logError("Unable to spawn PhantomJS; error code " + code);
}
};
for (var i = 0; i < phantomJSinstances; i++) {

@@ -154,3 +175,6 @@ var curProcess = spawn(path, args, {

curProcess.stderr.pipe(process.stderr);
curProcess.on('exit', checkPhantomjsSpawnExitCode);
// BACKWARD-COMPAT node 0.8 start
curProcess.on('exit', checkPhantomjsSpawnExitCode); // node 0.8
// BACKWARD-COMPAT node 0.8 end
curProcess.on('error', onPhantomJsSpawnError); // node 0.10
}

@@ -195,3 +219,9 @@ });

var success = (ignoreErrors || stats.errors === 0) && (ignoreFailures || stats.failures === 0);
logger.logInfo('Tests run: %d, Failures: %d, Errors: %d, Skipped: %d', [stats.testCases, stats.failures, stats.errors, stats.tasksIgnored]);
var msg = 'Tests run: ' + stats.testCases + ', ';
var msgFailures = stats.failures ? ('Failures: ' + stats.failures + ', ').red.bold : 'Failures: 0, '.green;
var msgErrors = stats.errors ? ('Errors: ' + stats.errors + ', ').red.bold : 'Errors: 0, '.green;
var msgSkipped = stats.tasksIgnored ? ('Skipped: ' + stats.tasksIgnored).yellow.bold : 'Skipped: 0'.green;
logger.logInfo(msg + msgFailures + msgErrors + msgSkipped);
endProcess(success);

@@ -205,3 +235,4 @@ });

flashPolicyPort: argv['flash-policy-port'],
flashPolicyServer: argv['flash-policy-server']
flashPolicyServer: argv['flash-policy-server'],
taskTimeout: argv['task-timeout']
}, logger);

@@ -212,4 +243,17 @@ testServer.server.on('error', function (error) {

});
testServer.server.listen(argv.port, function () {
testServer.addCampaign(campaign);
portfinder.basePort = argv.port;
portfinder.getPort(function (err, port) {
if (err) {
logger.logError("Can't start the server: " + err);
endProcess();
return;
}
if (port != argv.port && argv.port > 0) {
// logging error instead of a warning so it's more visible in the console
logger.logError("Port %d unavailable; using %d instead.", [argv.port, port]);
}
testServer.server.listen(port, function () {
testServer.addCampaign(campaign);
});
});

@@ -216,0 +260,0 @@ });

@@ -30,2 +30,3 @@ /*

var yaml = require('js-yaml');
var merge = require('./merge.js');

@@ -69,2 +70,6 @@

// Match '<%= FOO %>' where FOO is a propString, eg. foo or foo.bar but not
// a method call like foo() or foo.bar().
var propStringTmplRe = /<%=\s*([a-z0-9_$]+(?:\.[a-z0-9_$]+)*)\s*%>/gi;
/**

@@ -74,6 +79,7 @@ * Read the configuration file (if any) and generate a configuration object with the proper overrides.

* @param {Object} override Override some configuration parameters
* @param {Object} environment Environment configuration file
* @param {Object} logger
* @return {Object} Configuration object
*/
exports.readConfig = function (configFile, override, logger) {
exports.readConfig = function (configFile, override, environment, logger) {
var configData = getDefaults();

@@ -87,2 +93,11 @@ if (configFile) {

}
if (environment) {
environment = readConfigFile(environment, logger);
if (configFile !== false) {
if (!configData.env) {
configData.env = {};
}
merge(configData.env, environment);
}
}

@@ -101,3 +116,81 @@ if (override) {

}
return configData;
};
return recurse(configData, function (value) {
return replace(value, configData);
});
};
/**
* Recurse through objects and arrays executing a function for each non object.
* The return value replaces the original value
* @param {Object} value Object on which to recur
* @param {Function} fn Callback function
*/
function recurse(value, fn) {
if (Object.prototype.toString.call(value) === "[object Array]") {
return value.map(function (value) {
return recurse(value, fn);
});
} else if (Object.prototype.toString.call(value) === "[object Object]") {
var obj = {};
Object.keys(value).forEach(function (key) {
obj[key] = recurse(value[key], fn);
});
return obj;
} else {
return fn(value);
}
}
/**
* Get the value of a configuration parameter that might use templates
* @param {String} value Configuration value
* @param {Object} configData Container object
* @return {String} String with replacements
*/
function replace(value, configData) {
if (typeof value != "string") {
return value;
} else {
return value.replace(propStringTmplRe, function (match, path) {
var value = get(configData, path);
if (!(value instanceof Error)) {
return value;
} else {
return match;
}
});
}
}
// Keep a map of what I'm currently trying to get. Avoids circular references
var memoGet = {};
/**
* Get the value of a json object at a given path
* @param {Object} object Container object
* @param {String} path Path, delimited by dots
* @return {Object} value
*/
function get(object, path) {
if (memoGet[path]) {
return new Error("circular reference for " + path);
}
var parts = path.split(".");
var obj = object;
while (typeof obj === "object" && obj && parts.length) {
var part = parts.shift();
if (!(part in obj)) {
return new Error("invalid path");
}
obj = obj[part];
}
memoGet[path] = true;
// The replace can cause a circular reference
var value = replace(obj, object);
delete memoGet[path];
return value;
}

@@ -16,6 +16,8 @@ /*

var Minimatch = require('minimatch').Minimatch;
var fs = require('fs');
var pathUtils = require('path');
var util = require('util');
var Minimatch = require('minimatch').Minimatch;
var Enumerator = require('./enumerator.js');

@@ -66,3 +68,3 @@

var self = this;
var root = this._root;
var root = this._root || "";
var waitingCallbacks = 0;

@@ -69,0 +71,0 @@ var decreaseCallbacks = function () {

@@ -17,2 +17,3 @@ /*

require('colors');
var Logger = require('./logger.js');

@@ -19,0 +20,0 @@

@@ -16,8 +16,12 @@ /*

var ConsoleReport = function (logger) {
var ConsoleReport = function (logger, slowTestThreshold) {
this.logger = logger;
this.slowTestThreshold = slowTestThreshold || 0;
};
var eventTypes = {
campaignFinished: function campaignFinished(event) {
tasksList: function (event) {
this.logger.logInfo("Total %d tasks to be executed.", [event.remainingTasks]);
},
campaignFinished: function (event) {
this.logger.logInfo("Campaign finished.");

@@ -28,12 +32,24 @@ },

},
testFinished: function testFinished(event) {
if (!event.method) {
if (event.asserts != null) {
this.logger.logInfo("%s: %d assert(s)", [event.name, event.asserts]);
} else {
this.logger.logInfo("%s", [event.name]);
testFinished: function (event) {
if (event.method) {
return;
}
if (event.asserts != null) {
var msg = "[%s s] [%s] [%d left, %d total] %s: %d assert(s)";
if (this.slowTestThreshold && event.duration > this.slowTestThreshold) {
msg = msg.bold.inverse;
}
var duration = (event.duration / 1000).toFixed(2);
this.logger.logInfo(msg, [duration, event.browserName || "default browser", event.browserRemainingTasks - 1, event.remainingTasks - 1, event.name, event.asserts]);
} else {
this.logger.logInfo("%s", [event.name]);
}
},
error: function error(event) {
taskFinished: function (event) {
if (event.browserRemainingTasks === 0) {
this.logger.logInfo("All tasks finished for browser: ", [event.browserName || "default browser"]);
}
},
error: function (event) {
var name = event.name || "";

@@ -47,5 +63,5 @@ var method = event.method || "";

if (name) {
this.logger.logError("In %s: %s", [name, message]);
this.logger.logError("Error in %s: %s", [name, message]);
} else {
this.logger.logError("Error: %s", [name, message]);
this.logger.logError("Error: %s", [message]);
}

@@ -52,0 +68,0 @@ },

@@ -0,1 +1,16 @@

/*
* Copyright 2012 Amadeus s.a.s.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require('fs');

@@ -2,0 +17,0 @@

@@ -186,3 +186,3 @@ /*

test.endTime = event.time;
test.duration = test.endTime - test.startTime;
test.duration = event.duration;
this.stats.testsFinished++;

@@ -189,0 +189,0 @@ },

@@ -17,5 +17,6 @@ /*

var fs = require('fs');
var path = require('path');
var xmlReport = require('./xml-report.js');
var lcovReport = require('./lcov-report.js');
var path = require('path');

@@ -22,0 +23,0 @@ var writeReports = function (config, results, callback) {

@@ -19,4 +19,6 @@ /*

var parse = require('url').parse;
var connect = require('connect');
var send = require('send');
var connect = require('connect');
var Logger = require('./logging/logger.js');

@@ -23,0 +25,0 @@

@@ -18,14 +18,12 @@ /*

var Browser = function (config) {
this.name = config.name; // the browser name is only used for display in test results
if (!this.name) {
if (config.browserName) {
this.name = config.browserName;
if (config.majorVersion != null) {
this.name += " " + config.majorVersion;
if (config.minorVersion != null) {
this.name += "." + config.minorVersion;
if (config.revision != null) {
this.name += "." + config.revision;
}
var buildNameFromConfig = function (config) {
var name = "";
if (config.browserName) {
name = config.browserName;
if (config.majorVersion != null) {
name += " " + config.majorVersion;
if (config.minorVersion != null) {
name += "." + config.minorVersion;
if (config.revision != null) {
name += "." + config.revision;
}

@@ -35,4 +33,20 @@ }

}
if (config.os) {
name += " (" + config.os + ")";
}
return name;
};
var Browser = function (config) {
this.name = config.name || buildNameFromConfig(config); // the browser name is only used for display in test results
this.config = config;
/**
* Contains tasks not yet dispatched.
*/
this.tasksQueue = [];
/**
* Counts tasks not yet finished. Will be always >= this.tasksQueue.length
*/
this.pendingTasks = 0;
};

@@ -43,2 +57,3 @@

Browser.prototype.addTask = function (task) {
this.pendingTasks++;
this.tasksQueue.push(task);

@@ -59,2 +74,6 @@ };

Browser.prototype.onTaskFinished = function () {
this.pendingTasks--;
};
module.exports = Browser;

@@ -15,4 +15,10 @@ /*

var util = require("util");
var pathUtils = require("path");
var events = require("events");
var connect = require('connect');
var coverageServer = require('node-coverage').admin;
var Logger = require('../logging/logger.js');
var connect = require('connect');
var Coverage = require('./coverage.js');

@@ -22,6 +28,2 @@ var Resources = require('../resources.js');

var AllTests = require('../test-type/all-tests.js');
var util = require("util");
var pathUtils = require("path");
var events = require("events");
var coverageServer = require('node-coverage').admin;

@@ -37,4 +39,3 @@ var pad2 = function (number) {

var createCampaignId = function () {
var date = new Date();
var createCampaignId = function (date) {
return [date.getFullYear(), pad2(date.getMonth() + 1), pad2(date.getDate()), pad2(date.getHours()), pad2(date.getMinutes()), pad2(date.getSeconds())].join('');

@@ -44,14 +45,23 @@ };

var TestCampaign = function (config, parentLogger) {
this.id = createCampaignId();
this.startTime = new Date();
this.id = createCampaignId(this.startTime);
this.logger = new Logger('TestCampaign', this.id, parentLogger);
this.logger.logInfo("Initializing campaign...");
var rootDirectory = config.rootDirectory;
var rootDirectory = config.rootDirectory || "";
this.rootDirectory = rootDirectory;
this.config = config;
var browsersCfg = config.browsers || [{}];
if (config.browsers && config.browsers.length > 0) {
this.logger.logInfo("Expecting the following browsers to connect: ");
} else {
this.logger.logInfo("No specific browsers expected in the campaign");
}
var browsersCfg = config.browsers || [{}]; // if no browsers required, we create one, unrestricted
var browsers = [];
for (var i = 0, l = browsersCfg.length; i < l; i++) {
browsers[i] = new Browser(browsersCfg[i]);
if (browsers[i].name) {
this.logger.logInfo("- " + browsers[i].name);
}
}

@@ -131,2 +141,3 @@ this.browsers = browsers;

}
event.remainingTasks = this.remainingTasks;
this.emit('result-' + eventName, event);

@@ -148,3 +159,4 @@ this.emit('result', event);

});
self.logger.logInfo("Campaign finished.");
var elapsed = self.getElapsedTime();
self.logger.logInfo("Campaign finished. Time: %dmin %dsec", [elapsed.min, elapsed.sec]);
self.emit('finished');

@@ -169,2 +181,12 @@ });

TestCampaign.prototype.getElapsedTime = function () {
var elapsedSec = Math.ceil((new Date() - this.startTime) / 1000);
var elapsedMin = Math.floor(elapsedSec / 60);
elapsedSec = elapsedSec % 60;
return {
min: elapsedMin,
sec: elapsedSec
};
};
module.exports = TestCampaign;

@@ -15,2 +15,4 @@ /*

var fs = require('fs');
var pathUtils = require('path');
var coverage = require('node-coverage');

@@ -20,5 +22,5 @@ var instrument = coverage.instrument;

var coverageServer = coverage.admin;
var fs = require('fs');
var pathUtils = require('path');
var FileSet = require('../fileset.js');
var merge = require('../merge.js');

@@ -169,5 +171,3 @@

res.setHeader('Content-type', 'text/javascript');
res.write(data, function () {
res.end();
});
res.end(data);
});

@@ -174,0 +174,0 @@ return;

@@ -26,19 +26,38 @@ /*

var statusBar = document.getElementById('status_text');
var statusInfo = document.getElementById('status_info');
var pauseResume = document.getElementById('pause_resume');
var logs = document.getElementById('status_logs');
var paused = false;
var iframe = document.getElementById('iframe');
var emptyUrl = location.href.replace(/\/slave\.html/, '/empty.html');
var iframeParent = document.getElementById('content');
var iframe;
var baseUrl = location.protocol + "//" + location.host;
var socketStatus = "loading";
var testStatus = "waiting";
var testInfo = "loading";
var currentTask = null;
var pendingTestStarts = null;
var beginning = new Date();
var log = window.location.search.indexOf("log=true") !== -1 ?
function (message) {
var time = (new Date() - beginning) + "ms";
// Log to the browser console
if (window.console && window.console.log) {
console.log(time, message);
}
// Log to the div console (useful for remote slaves or when console is missing)
logs.innerHTML = "<p><span class='timestamp'>" + time + "</span>" + message + "</p>" + logs.innerHTML;
logs.firstChild.scrollIntoView(false);
} : function () {};
var updateStatus = function () {
statusBar.innerHTML = "Attester - " + socketStatus + " - " + testStatus;
statusBar.innerHTML = socketStatus + " - " + testStatus;
pauseResume.innerHTML = paused ? "Resume" : "Pause";
statusInfo.innerHTML = "<span id='_info_" + testInfo + "'></span>";
};
var socketStatusUpdater = function (status) {
var socketStatusUpdater = function (status, info) {
return function (param) {
socketStatus = param ? status.replace('$', param) : status;
testInfo = info || status;
updateStatus();

@@ -48,9 +67,28 @@ };

var removeIframe = function () {
if (iframe) {
iframeParent.removeChild(iframe);
iframe = null;
}
};
var createIframe = function (src) {
removeIframe();
iframe = document.createElement("iframe");
iframe.setAttribute("id", "iframe");
iframe.setAttribute("src", src);
iframe.setAttribute("frameborder", "0");
iframeParent.appendChild(iframe);
};
var stop = function () {
currentTask = null;
iframe.src = emptyUrl;
pendingTestStarts = null;
removeIframe();
testStatus = paused ? "paused" : "waiting";
testInfo = "idle";
updateStatus();
};
log("creating a socket");
var socket = io.connect(location.protocol + '//' + location.host, {

@@ -63,2 +101,3 @@ 'reconnection delay': 500,

socket.on('connect', function () {
log("slave connected");
attester.connected = true;

@@ -72,8 +111,9 @@ stop();

});
socketStatusUpdater('connected');
});
socket.on('connect', socketStatusUpdater('connected'));
socket.on('disconnect', socketStatusUpdater('disconnected'));
socket.on('disconnect', function () {
log("slave disconnected");
attester.connected = false;
socketStatusUpdater('disconnected');
});

@@ -83,6 +123,8 @@ if (config.onDisconnect) {

}
socket.on('reconnecting', socketStatusUpdater('reconnecting in $ ms...'));
socket.on('reconnect', socketStatusUpdater('re-connected'));
socket.on('reconnect_failed', socketStatusUpdater('failed to reconnect'));
socket.on('reconnecting', socketStatusUpdater('reconnecting in $ ms...', 'disconnected'));
socket.on('reconnect', socketStatusUpdater('re-connected', 'connected'));
socket.on('reconnect_failed', socketStatusUpdater('failed to reconnect', 'disconnected'));
socket.on('server_disconnect', function () {
log("server disconnected");
testInfo = "disconnected";
socket.socket.disconnect();

@@ -93,6 +135,12 @@ socket.socket.reconnect();

currentTask = data;
iframe.src = "empty.html";
testStatus = "executing " + data.name;
pendingTestStarts = {};
removeIframe();
testStatus = "executing " + data.name + " remaining " + data.stats.remainingTasks + " tasks in this campaign";
if (data.stats.browserRemainingTasks != data.stats.remainingTasks) {
testStatus += ", including " + data.stats.browserRemainingTasks + " tasks for this browser";
}
testInfo = "executing";
updateStatus();
iframe.src = baseUrl + data.url;
log("<i>slave-execute</i> task <i>" + data.name + "</i>, " + data.stats.remainingTasks + " tasks left");
createIframe(baseUrl + data.url);
});

@@ -103,2 +151,3 @@ socket.on('slave-stop', stop);

pauseResume.onclick = function () {
log("toggle pause status");
paused = !paused;

@@ -118,2 +167,20 @@ updateStatus();

info.event = name;
log("sending test update <i class='event'>" + name + "</i> for test <i>" + info.name + "</i>");
if (name === "testStarted") {
if (pendingTestStarts.hasOwnProperty(info.testId)) {
log("<i>warning</i> this <i>testStarted</i> is (wrongly) reusing a previous testId: <i>" + info.testId + "</i>");
}
pendingTestStarts[info.testId] = info;
} else if (name === "testFinished") {
var previousTestStart = pendingTestStarts[info.testId];
if (!previousTestStart) {
log("<i>warning</i> this <i>testFinished</i> is ignored as it has no previous <i>testStarted</i>");
return;
}
pendingTestStarts[info.testId] = false;
info.duration = info.time - previousTestStart.time;
}
if (name === "error") {
log("<i class='error'>error</i> message: <i>" + info.error.message + "</i>");
}
socket.emit('test-update', info);

@@ -120,0 +187,0 @@ };

@@ -16,7 +16,11 @@ /*

var http = require('../http-server.js');
var url = require('url');
var path = require('path');
var fs = require("fs");
var connect = require('connect');
var socketio = require('socket.io');
var _ = require("lodash");
var http = require('../http-server.js');
var Slave = require('./slave.js');

@@ -26,2 +30,7 @@ var Logger = require('../logging/logger.js');

// middlewares
var noCache = require("../middlewares/noCache");
var index = require("../middlewares/index");
var template = require("../middlewares/template");
var arrayRemove = function (array, item) {

@@ -36,9 +45,2 @@ var index = array.indexOf(item);

var noCache = function (req, res, next) {
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', (new Date(0)).toString());
next();
};
var socketConnection = function (socket) {

@@ -48,3 +50,3 @@ var testServer = this;

if (data.type == 'slave') {
var newSlave = new Slave(socket, data);
var newSlave = new Slave(socket, data, testServer.config);
testServer.addSlave(newSlave);

@@ -55,42 +57,2 @@ }

var statusPage = function (req, res, next) {
if (req.path != '/status.html') {
return next();
}
var testServer = this;
res.setHeader('Content-Type', 'text/html;charset=utf-8');
var content = ['<!doctype html>\n<html><head><title>Attester status</title>'];
content.push('<meta http-equiv="Refresh" content="15" />');
content.push('</head><body><h1>Attester status</h1>');
var slaves = testServer.slaves,
l = slaves.length;
content.push('<h2>Connected browsers</h2>');
if (l > 0) {
content.push('<table><tr><th style="width:150px;">Browser</th><th style="width:300px;">User agent</th><th style="width:150px;">Address</th><th>Status</th></tr>');
for (var i = 0; i < l; i++) {
var curSlave = slaves[i];
content.push('<tr>');
content.push('<td>', curSlave.displayName, '</td>');
content.push('<td>', curSlave.userAgent, '</td>');
content.push('<td>', curSlave.addressName || curSlave.address, ':', curSlave.port, '</td>');
content.push('<td>', curSlave.getStatus(), '</td>');
content.push('</tr>');
}
content.push('</table>');
} else {
content.push('There is no connected browser.');
}
var campaign = testServer.campaigns[0];
if (campaign) {
content.push('<h2>Campaign status</h2>');
content.push('Total tasks: ', campaign.tasks.length, '<br />');
content.push('Remaining tasks: ', campaign.remainingTasks, '<br />');
}
content.push('</body></html>');
content = content.join('');
res.write(content);
res.end();
return;
};
var routeToCampaign = function (req, res, next) {

@@ -184,4 +146,17 @@ var testServer = this;

app.use(noCache);
app.use(connect.compress());
app.use(index);
app.use(connect.favicon(path.join(__dirname, "client", "favicon.ico")));
// Template pages (before the static folder)
app.use('/__attester__', template.bind({
data: this,
page: "/index.html",
path: path.join(__dirname, "client", "index.html")
}));
app.use('/__attester__', template.bind({
data: this,
page: "/status.html",
path: path.join(__dirname, "client", "status.html")
}));
app.use('/__attester__', connect['static'](__dirname + '/client'));
app.use('/__attester__', statusPage.bind(this));
app.use('/__attester__/coverage/data', routeCoverage.bind(this));

@@ -246,2 +221,3 @@ app.use(routeToCampaign.bind(this));

TestServer.prototype.addSlave = function (slave) {
this.logger.logInfo("New slave connected: " + slave.toString());
this.slaves.push(slave);

@@ -248,0 +224,0 @@ if (slave.isAvailable()) {

@@ -18,5 +18,6 @@ /*

var events = require("events");
var detectBrowser = require("../browser-detection.js").detectBrowser;
var dns = require("dns");
var detectBrowser = require("../browser-detection.js").detectBrowser;
var allowedTestUpdateEvents = {

@@ -29,3 +30,50 @@ "testStarted": 1,

var Slave = function (socket, data) {
var wrapInTryCatch = function (scope, fct) {
return function () {
try {
return fct.apply(scope, arguments);
} catch (e) {
console.log("Error when called from " + scope + ": ", e.stack || e);
}
};
};
var campaignTaskFinished = function (scope, campaign, task) {
var event = {
event: "taskFinished"
};
task.browser.onTaskFinished();
feedEventWithTaskData(event, task);
campaign.addResult(event);
};
var emitTaskError = function (scope, message) {
var task = scope.currentTask;
if (task) {
var campaign = scope.currentCampaign;
campaign.addResult({
event: "error",
taskId: task.taskId,
taskName: task.test.name,
error: {
message: message
},
name: task.test.name
});
campaignTaskFinished(scope, campaign, task);
scope.currentTask = null;
scope.currentCampaign = null;
}
};
var feedEventWithTaskData = function (event, task) {
event.taskId = task.taskId;
event.taskName = task.test.name;
event.browserName = task.browser.name;
event.browserRemainingTasks = task.browser.pendingTasks;
return event;
};
var Slave = function (socket, data, config) {
this.config = config;
this.socket = socket;

@@ -42,7 +90,8 @@ this.userAgent = data.userAgent;

this.paused = !! data.paused;
this.taskTimeoutId = null;
dns.reverse(this.address, this.onReceiveAddressName.bind(this));
socket.on('disconnect', this.onSocketDisconnected.bind(this));
socket.on('test-update', this.onTestUpdate.bind(this));
socket.on('task-finished', this.onTaskFinished.bind(this));
socket.on('pause-changed', this.onPauseChanged.bind(this));
socket.on('disconnect', wrapInTryCatch(this, this.onSocketDisconnected));
socket.on('test-update', wrapInTryCatch(this, this.onTestUpdate));
socket.on('task-finished', wrapInTryCatch(this, this.onTaskFinished));
socket.on('pause-changed', wrapInTryCatch(this, this.onPauseChanged));
};

@@ -52,2 +101,7 @@

Slave.prototype.toString = function () {
var os = this.browserInfo.os.name;
return (this.addressName || this.address) + ":" + this.port + " (" + this.displayName + "; " + os + ")";
};
Slave.prototype.onReceiveAddressName = function (err, names) {

@@ -69,3 +123,7 @@ if (!err && names.length > 0) {

taskId: task.taskId,
campaignId: campaign.id
campaignId: campaign.id,
stats: {
remainingTasks: campaign.remainingTasks,
browserRemainingTasks: task.browser.pendingTasks
}
});

@@ -83,11 +141,9 @@ this.currentCampaign.addResult({

});
this.taskTimeoutId = setTimeout(this.taskTimeout.bind(this), this.config.taskTimeout);
};
Slave.prototype.onTestUpdate = function (event) {
event = event || {};
var eventName = event.event;
if (allowedTestUpdateEvents.hasOwnProperty(eventName)) {
var task = this.currentTask;
event.taskId = task.taskId;
event.taskName = task.test.name;
feedEventWithTaskData(event, this.currentTask);
this.currentCampaign.addResult(event);

@@ -109,18 +165,3 @@ }

var task = this.currentTask;
if (task) {
var campaign = this.currentCampaign;
campaign.addResult({
event: "error",
taskId: task.taskId,
taskName: task.test.name,
error: {
message: "Browser was disconnected."
}
});
campaign.addResult({
event: "taskFinished",
taskId: task.taskId,
taskName: task.test.name
});
}
emitTaskError(this, "Browser was disconnected: " + this.toString());
this.emit('disconnect');

@@ -134,9 +175,5 @@ };

this.currentCampaign = null;
this.subTestsById = null;
clearTimeout(this.taskTimeoutId);
this.socket.emit('slave-stop');
campaign.addResult({
event: "taskFinished",
taskId: task.taskId,
taskName: task.test.name
});
campaignTaskFinished(this, campaign, task);
if (!this.paused) {

@@ -165,2 +202,10 @@ this.emit('available');

Slave.prototype.taskTimeout = function () {
this.socket.emit('slave-stop');
emitTaskError(this, "Task timeout.");
if (!this.paused) {
this.emit('available');
}
};
module.exports = Slave;

@@ -49,2 +49,3 @@ /*

this.initResults = [];
this.debugUrls = [];
};

@@ -147,3 +148,6 @@

var tasksTrees = buildTasksArrays.call(this, testType, testType.testsTrees);
// Only add a node for the test type if there several types are defined:
if (tasksTrees.length === 0) {
this.logger.logWarn("There are no tasks defined for test type '%s'. Please check the configuration", [testType.name]);
}
// Only add a node for the test type if several types are defined:
if (this.testTypesCount == 1) {

@@ -157,2 +161,8 @@ this.tasksTrees = tasksTrees;

}
if (testType.debug) {
this.debugUrls.push({
name: testType.name,
url: testType.debug
});
}
decrementExpectedCallbacks();

@@ -159,0 +169,0 @@ };

@@ -18,2 +18,3 @@ /*

var fs = require('fs');
var readATFileContent = require('./at-file-reader.js');

@@ -20,0 +21,0 @@

@@ -16,11 +16,12 @@ /*

var BaseTestType = require('../base-test-type.js');
var util = require('util');
var path = require('path');
var connect = require('connect');
var BaseTestType = require('../base-test-type.js');
var TestsEnumerator = require('./at-tests-enumerator.js');
var fs = require('fs');
var path = require('path');
var url = require('url');
var querystring = require('querystring');
var merge = require('../../merge.js');
var template = require('../../middlewares/template');

@@ -40,41 +41,2 @@ var sendAttesterTestSuite = function (req, res, next) {

var sendTest = function (req, res, next) {
var parsedUrl = url.parse(req.url);
var automatic = (parsedUrl.pathname == '/test.html');
if (!automatic) {
if (parsedUrl.pathname == '/MainTestSuite.js') {
return sendAttesterTestSuite.call(this, req, res, next);
}
if (parsedUrl.pathname != '/interactive.html') {
return next();
}
}
var query = querystring.parse(parsedUrl.query);
res.setHeader('Content-Type', 'text/html;charset=utf-8');
var content = ['<!doctype html>\n<html><head><title>Aria Templates tester</title>'];
content.push('<meta http-equiv="X-UA-Compatible" content="IE=edge" />');
content.push('<script type="text/javascript">var Aria=', JSON.stringify(this.ariaConfig), ';</script>');
content.push('<script type="text/javascript" src="', escape(this.bootstrap), '"></script>');
var extraScripts = this.extraScripts;
for (var i = 0, l = extraScripts.length; i < l; i++) {
content.push('<script type="text/javascript" src="', escape(extraScripts[i]), '"></script>');
}
if (automatic) {
content.push('<script type="text/javascript">var __testClasspath=', JSON.stringify(query.testClasspath || ""), ';</script>');
content.push('<script type="text/javascript" src="/__attester__/aria-templates/run.js"></script>');
} else {
content.push('<style>html{overflow:hidden;} body{margin:0px;background-color:#FAFAFA;font-family: tahoma,arial,helvetica,sans-serif;font-size: 11px;}</style>');
}
content.push('</head><body>');
if (automatic) {
content.push('<div id="TESTAREA"></div>');
} else {
content.push('<div id="root"></div><script type="text/javascript">var width={min:180};var height={min:342};aria.core.DownloadMgr.updateRootMap({"MainTestSuite":"./"});aria.core.DownloadMgr.updateUrlMap({"MainTestSuite":"MainTestSuite.js"});Aria.loadTemplate({rootDim:{width:width,height:height},classpath:"aria.tester.runner.view.main.Main",div:"root",width:width,height:height,moduleCtrl:{classpath:"aria.tester.runner.ModuleController"}});</script>');
}
content.push('</body></html>');
content = content.join('');
res.write(content);
res.end();
};
var AriaTemplatesTests = function (campaign, inputConfig) {

@@ -123,6 +85,29 @@ // Default values for the config:

var app = connect();
app.use('/__attester__/aria-templates', sendTest.bind(this));
app.use('/__attester__/aria-templates', template.bind({
data: {
ariaConfig: JSON.stringify(ariaConfig),
config: testConfig
},
page: "/test.html",
path: path.join(__dirname, "templates", "testPage.html")
}));
app.use('/__attester__/aria-templates', template.bind({
data: {
ariaConfig: JSON.stringify(ariaConfig),
config: testConfig
},
page: "/interactive.html",
path: path.join(__dirname, "templates", "interactive.html")
}));
app.use('/__attester__/aria-templates', (function (req, res, next) {
if ("/MainTestSuite.js" === req.url) {
sendAttesterTestSuite.call(this, req, res, next);
} else {
next();
}
}).bind(this));
app.use('/__attester__/aria-templates', connect['static'](path.join(__dirname, 'client')));
this.handleRequest = app.handle.bind(app);
this.debug = "/__attester__/aria-templates/interactive.html";
};

@@ -129,0 +114,0 @@

@@ -16,7 +16,8 @@ /*

var path = require('path');
var util = require('util');
var ATEnvironment = require('./at-environment.js');
var FileSet = require('../../fileset.js');
var path = require('path');
var Logger = require('../../logging/logger.js');
var util = require('util');

@@ -23,0 +24,0 @@ var arrayToMap = function (array) {

@@ -38,3 +38,4 @@ /*

return [];
}
},
coverage: function () {}
};

@@ -52,2 +53,11 @@ }

var reportException = function (message, exception) {
attester.testError({
error: {
message: message + ": " + exception,
stack: attester.stackTrace(exception)
}
});
};
if (!Aria || !Aria.load) {

@@ -209,8 +219,3 @@ fatalError("The Aria Templates framework could not be loaded.");

} catch (e) {
attester.testError({
error: {
message: "Error while disposing test " + testClasspath,
stack: attester.stackTrace(e)
}
});
reportException("Error while disposing test " + testClasspath, e);
}

@@ -246,8 +251,3 @@ if (Aria.memCheckMode) {

} catch (e) {
attester.testError({
error: {
message: "Error while disposing Aria Templates: " + e,
stack: attester.stackTrace(e)
}
});
reportException("Error while disposing Aria Templates", e);
}

@@ -275,3 +275,9 @@ }

function startTest() {
mainTestObject = Aria.getClassInstance(testClasspath);
try {
mainTestObject = Aria.getClassInstance(testClasspath);
} catch (e) {
reportException("Error while instantiating test " + testClasspath, e);
attester.taskFinished();
return;
}
registerTest(mainTestObject);

@@ -278,0 +284,0 @@ mainTestObject.run();

@@ -64,2 +64,8 @@ /*

/**
* URL of the debug interface where it should be possible to manually run tests.
* @type String
*/
BaseTestType.prototype.debug = "";
module.exports = BaseTestType;

@@ -16,12 +16,10 @@ /*

var BaseTestType = require('../base-test-type.js');
var util = require('util');
var connect = require('connect');
var path = require('path');
var url = require('url');
var merge = require('../../merge.js');
var BaseTestType = require('../base-test-type.js');
var FileSet = require('../../fileset');
var querystring = require('querystring');
var config;
var merge = require('../../merge.js');

@@ -38,28 +36,3 @@ /**

*/
var sendTest = function (req, res, next) {
var parsedUrl = url.parse(req.url);
if (parsedUrl.pathname !== '/test.html') {
// We're asking for one the resources
return next();
}
// Send the test
var query = querystring.parse(parsedUrl.query);
res.setHeader('Content-Type', 'text/html;charset=utf-8');
var content = ['<!doctype html>\n<html><head><title>Mocha tests</title>'];
content.push('<meta http-equiv="X-UA-Compatible" content="IE=edge" />');
content.push('<script src="' + config.assertion + '"></script>');
content.push('<link rel="stylesheet" href="/__attester__/mocha/lib/mocha.css" />');
content.push('<script src="/__attester__/mocha/lib/mocha.js"></script>');
content.push('<script src="/__attester__/mocha/client/connector.js"></script>');
content.push('<script>mocha.setup(' + buildSetupConfig(config) + ');</script>');
content.push('</head><body>');
content.push('<div id="mocha"></div>');
content.push('<div id="TESTAREA"></div>');
content.push('<script src="/__attester__/mocha/tests/' + query.name + '"></script>');
content.push('<script>mocha.run();</script>');
content.push('</body></html>');
content = content.join('');
res.write(content);
res.end();
};
var template = require('../../middlewares/template');

@@ -92,2 +65,3 @@ /**

},
extraScripts: [],
ui: 'bdd',

@@ -99,22 +73,41 @@ ignoreLeaks: false,

merge(testConfig, inputConfig);
config = testConfig;
BaseTestType.call(this, campaign, config);
BaseTestType.call(this, campaign, testConfig);
var app = connect();
app.use('/__attester__/mocha', sendTest.bind(this));
var mochaSetup = buildSetupConfig(testConfig);
app.use('/__attester__/mocha', template.bind({
data: {
config: testConfig,
mochaSetup: mochaSetup
},
page: "/test.html",
path: path.join(__dirname, "templates", "testPage.html")
}));
app.use('/__attester__/mocha/lib', connect['static'](path.dirname(require.resolve('mocha'))));
app.use('/__attester__/mocha/client', connect['static'](path.join(__dirname, 'client')));
app.use('/__attester__/mocha/tests', connect['static'](path.join(__dirname, path.relative(__dirname, testConfig.rootDirectory))));
app.use('/__attester__/mocha/tests', connect['static'](path.join(__dirname, path.relative(__dirname, testConfig.files.rootDirectory))));
var assertion = config.assertion;
var assertion = testConfig.assertion;
if (/^expect([\.\-]?js)?$/.test(assertion)) {
config.assertion = '/__attester__/mocha/assertion/expect.js';
testConfig.assertion = '/__attester__/mocha/assertion/expect.js';
app.use('/__attester__/mocha/assertion', connect['static'](path.dirname(require.resolve('expect.js'))));
} else if (/chai(\.js)?/.test(assertion)) {
config.assertion = '/__attester__/mocha/assertion/chai.js';
testConfig.assertion = '/__attester__/mocha/assertion/chai.js';
app.use('/__attester__/mocha/assertion', connect['static'](path.dirname(require.resolve('chai'))));
}
app.use('/__attester__/mocha', template.bind({
data: {
config: testConfig,
mochaSetup: mochaSetup,
// The test list is available only after initialization
tests: this.testsTrees
},
page: "/interactive.html",
path: path.join(__dirname, "templates", "interactive.html")
}));
this.handleRequest = app.handle.bind(app);
this.debug = "/__attester__/mocha/interactive.html";
this.extraScripts = testConfig.extraScripts;
};

@@ -126,2 +119,6 @@

/**
* Initialize this test script by extending the file sets in the configuration object.
* @param {Function} callback
*/
MochaTestType.prototype.init = function (callback) {

@@ -128,0 +125,0 @@ var testType = this;

@@ -5,3 +5,3 @@ {

"description": "Command line tool to run Javascript tests in several web browsers.",
"version": "1.1.0",
"version": "1.2.0",
"homepage": "http://github.com/ariatemplates/attester",

@@ -16,24 +16,26 @@ "repository": {

"dependencies": {
"colors": "0.6.0-1",
"connect": "2.7.4",
"js-yaml": "2.0.3",
"socket.io": "0.9.14",
"lodash": "1.2.0",
"minimatch": "0.2.11",
"node-coverage": "1.0.2",
"optimist": "0.3.5",
"connect": "2.7.4",
"minimatch": "0.2.11",
"colors": "0.6.0-1",
"portfinder": "0.2.1",
"send": "0.1.0",
"node-coverage": "1.0.2",
"ua-parser": "0.3.2"
"socket.io": "0.9.14",
"ua-parser": "0.3.3"
},
"devDependencies": {
"jasmine-node": "1.4.x",
"ariatemplates": "1.3.7",
"chai": "1.5.0",
"expect.js": "0.2.0",
"grunt": "0.4.1",
"grunt-jasmine-node": "0.0.6",
"grunt-beautify": "git+https://github.com/ariatemplates/grunt-beautify.git#grunt4",
"grunt-cli": "0.1.7",
"grunt-contrib-jshint": "0.3.0",
"grunt-contrib-watch": "0.3.1",
"mocha": "1.8.1",
"expect.js": "0.2.0",
"chai": "1.5.0",
"grunt-cli": "0.1.7"
"grunt-jasmine-node": "0.1.0",
"jasmine-node": "1.7.0",
"mocha": "1.8.1"
},

@@ -43,5 +45,5 @@ "scripts": {

},
"engines" : {
"node" : "~0.8.3"
"engines": {
"node": "~0.8.3"
}
}

@@ -29,2 +29,35 @@ Attester

License
-------
[Apache License 2.0](https://github.com/ariatemplates/attester/blob/master/LICENSE)
Dependencies & installation
---------------------------
You'll need [node.js](http://nodejs.org/download/). Attester is available in [npm repository](https://npmjs.org/package/attester); you can use `npm install attester` in your console, or clone this repository and then issue `npm install`.
If you want to make use of PhantomJS headless testing, you'll additionally need to [install PhantomJS](http://phantomjs.org/download.html) and make sure it's in your `PATH`.
### Installing PhantomJS on Windows
1. Download [phantomjs-1.9.1-windows.zip](https://phantomjs.googlecode.com/files/phantomjs-1.9.1-windows.zip) and extract it.
2. Move the contents of `phantomjs-1.9.1-windows` to `C:\bin\phantomjs`
3. Add `C:\bin\phantomjs` to `PATH`
4. Check that it works by issuing `phantomjs --version` in `cmd`
### Installing PhantomJS on Ubuntu
Quick setup on 64bit Ubuntu:
cd /usr/local/share/
sudo wget http://phantomjs.googlecode.com/files/phantomjs-1.9.1-linux-x86_64.tar.bz2
sudo tar jxvf phantomjs-1.9.1-linux-x86_64.tar.bz2
sudo ln -s /usr/local/share/phantomjs-1.9.1-linux-x86_64/ /usr/local/share/phantomjs
sudo ln -s /usr/local/share/phantomjs/bin/phantomjs /usr/local/bin/phantomjs
If you have 32bit version, replace `x86_64` with `i686` in the commands above.
Check that it works by issuing `phantomjs --version` in console.
Usage

@@ -104,3 +137,9 @@ -----

- browserName: 'Chrome'
# It's possible to distinguish browsers by operating systems, read more below
- browserName: 'Firefox'
os: 'Windows 7'
- browserName: 'Firefox'
os: 'Desktop Linux'
- browserName: 'Firefox'
os: 'Android'
- browserName: 'Opera'

@@ -115,2 +154,4 @@ - browserName: 'Safari'

majorVersion: 9
- browserName: 'IE'
majorVersion: 10
# Note that 'minorVersion' and 'revision' are also available

@@ -120,2 +161,68 @@ # The 'name' property allows to change the display name of the browser in the reports.

#### Regarding browser detection by operating system:
Attester uses [ua-parser](https://github.com/tobie/ua-parser/) for that and supports operating system families as returned by ua-parser, e.g.:
- 'Windows XP'
- 'Windows Vista'
- 'Windows 7'
- 'Windows 8'
- 'Mac OS X'
- 'Ubuntu'
- 'Debian'
- 'Fedora'
- 'Chrome OS'
- 'iOS'
- 'Android'
- 'Windows Phone'
- 'Firefox OS'
- 'BlackBerry OS'
For convenience, two additional values are accepted by Attester:
- 'Desktop Linux' (Linux, but not Android or webOS)
- 'Desktop Windows' (Windows, but not Windows Phone)
For a bigger (though not complete) list of options, you can have a look at the test resources in ua-parser: [resource 1](https://raw.github.com/tobie/ua-parser/master/test_resources/test_user_agent_parser_os.yaml) and [resource 2](https://raw.github.com/tobie/ua-parser/master/test_resources/additional_os_tests.yaml).
**Environment Variables**
It is possible to build a special portion of the configuration object from an external file using `--env` option.
````
attester --env package.json
````
This puts the content of `package.json` inside the `env` section of attester configuration. It is then possible to reference such values with simple templates.
`<%= prop.subprop %>` Expands to the value of `prop.subprop` in the config. Such templates can only be used inside string values, either in arrays or objects
````json
{
"resources": {
"/": "src"
},
"tests": {
"mocha": {
"files": {
"includes": ["spec/**/*"]
}
}
},
"coverage": {
"files": {
"rootDirectory": "src",
"includes": ["**/*.js"]
}
},
"coverage-reports": {
"json-file": "/reports/coverage-<%= env.version %>.json"
}
}
````
The configuration above generates a coverage report file called `coverage-x.y.z.json` where `x.y.z` is the value taken from `package.json` or any other file passed referenced by `--env`.
Template options can be used both in `yaml` and `json` file and the environment file can be of any of the two format.
**Usual options:**

@@ -130,2 +237,3 @@

`--phantomjs-instances <number>` Number of instances of [PhantomJS](http://phantomjs.org/) to start (default: `0`).
Additionally, a string `auto` can be passed to let the program use the optimal number of instances for best performance (max. 1 per CPU thread).

@@ -136,2 +244,4 @@ `--browser <path>` Path to any browser executable to execute the tests. Can be repeated multiple times to start multiple

`--env <path>` Path to a `yaml` or `json` file containing environment options. See section above.
`--ignore-errors` When enabled, test errors (not including failures) will not cause this process to return a non-zero value.

@@ -156,7 +266,9 @@

`--json-console` When enabled, JSON objects will be sent to stdout to provide information about tests.
`--json-console` When enabled, JSON objects will be sent to `stdout` to provide information about tests.
This is used by the [junit bridge](https://github.com/ariatemplates/attester-junit).
`--heartbeats` Delay (in ms) for heartbeats messages sent when --json-console is enabled. Use 0 to disable them.
`--heartbeats` Delay (in ms) for heartbeats messages sent when `--json-console` is enabled. Use `0` to disable them.
`--task-timeout` Maximum duration (in ms) for a test task.
**Configuration file options**

@@ -168,1 +280,80 @@

attester --config.resources./ path1/root1 --config.resources./ path2/root2 --config.resources./aria path/to/aria
Test Page
---------
It's important to understand exactly what is served by attester to a connected browser.
A slave browser receives a page with a single test and the files necessary to run it.
The test page has access to anything that is listed in `resources`.
From the configuration file
`tests` is used to build a list of tests for each testing framework
`resources` simply defines what is accessible by the page, none of these files is automatically added to the page
Inside `test` there are two parameters that deal with files
* `files` used to generate the list of test files
* `extraScripts` resources to be included in the test page
It's worth noticing that `extraScripts` must be an absolute path from the server root (what is configured in the resources).
Let's see one example
````json
{
"resources" : {
"/" : ["src"],
"/test" : ["spec"]
}
}
````
The block above configures attester to serve any file inside `src` folder from the server root, and files from `spec` from `/test`.
````json
{
"resources" : {
"/" : ["src"],
"/test" : ["spec"]
},
"tests" : {
"mocha" : {
"files" : {
"includes" : ["spec/**/*.js"]
}
}
}
}
````
This block tells attester that each JavaScript file in the spec folder is a `mocha` test. When dispatching them to connected browsers, each will receive a page containing
* mocha
* an attester adapter to mocha tests
* a single test file
Including source files it's up to the test that can use script loaders like [noder](https://github.com/ariatemplates/noder), [curl](https://github.com/cujojs/curl), [head.js](http://headjs.com/) or similar.
Using a script loader allows to include in the tested page only what's strictly necessary for the test.
It's also possible to configure attester to include other scripts then the test through `extraScripts`.
````json
{
"resources": {
"/": ["src"],
"/test": ["spec"]
},
"tests": {
"mocha": {
"files": {
"includes": ["spec/**/*.js"]
},
"extraScripts": ["/sourceCode.js", "/utilities.js"]
}
}
}
````
The paths specified in `extraScripts` are resources URL. In the example above `sourceCode.js` must be physically located inside `src` folder.
The files specified in `extraScripts` are included in each and every test.

@@ -0,1 +1,2 @@

/*globals describe, it, runs, waitsFor, expect*/
/*

@@ -24,2 +25,14 @@ * Copyright 2012 Amadeus s.a.s.

var findErrorMessage = function (needle, haystack) {
// Some errors logged can be verbose and env-dependent, hence we check if there's
// an error starting with particular string rather than being equal to it.
for (var idx = 0; idx < haystack.length; idx++) {
var item = haystack[idx];
if (item.indexOf(needle) === 0) { // item.startsWith(needle)
return idx;
}
}
return -1;
};
var itRuns = function (options) {

@@ -31,2 +44,3 @@ it(options.testCase, function () {

var testExecution = null;
var errorMessages = [];
runs(function () {

@@ -37,2 +51,3 @@ console.log('\n---------------------------------------');

var args = [execPath].concat(options.args || []);
args.push("--phantomjs-instances", "1");
var spawnOpts = options.spawnOpts || {};

@@ -57,3 +72,3 @@ var timeout = null;

// data is a buffer
data = data.toString();
data = data.toString().replace(/\033\[[0-9]*m/ig, ""); // strip ANSI color codes
var result = data.match(/tests run\s?\:\s?(\d+)\s?,\s?failures\s?\:\s?(\d+)\s?,\s?errors\s?\:\s?(\d+)\s?,\s?skipped\s?\:\s?(\d+)\s?/i);

@@ -68,2 +83,6 @@ if (result) {

}
var errors = data.match(/Error( in .*?)?:(.*)/); // non-greedy match in case error has more :
if (errors) {
errorMessages.push(errors[2].trim());
}
});

@@ -83,2 +102,16 @@ timeout = setTimeout(function () {

}
if (options.hasErrors) {
for (var i = 0; i < options.hasErrors.length; i += 1) {
var indexOfError = findErrorMessage(options.hasErrors[i], errorMessages);
if (indexOfError === -1) {
throw new Error("Error " + options.hasErrors[i] + " was not found in logs.");
} else {
// error found, remove it
errorMessages.splice(indexOfError, 1);
}
}
if (errorMessages.length > 0) {
throw new Error("Unexpected error message " + errorMessages[0]);
}
}
});

@@ -91,3 +124,3 @@ });

exitCode: 0,
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldSucceed', '--phantomjs-instances', '1', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldSucceed', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
results: {

@@ -104,3 +137,3 @@ run: 1,

exitCode: 1,
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldFail', '--phantomjs-instances', '1', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldFail', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
results: {

@@ -117,3 +150,3 @@ run: 1,

exitCode: 0,
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldFail', '--phantomjs-instances', '1', '--ignore-failures', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldFail', '--ignore-failures', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
results: {

@@ -130,3 +163,3 @@ run: 1,

exitCode: 1,
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldRaiseError', '--phantomjs-instances', '1', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldRaiseError', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
results: {

@@ -141,5 +174,17 @@ run: 1,

itRuns({
testCase: 'hasErrorInConstructor',
exitCode: 1,
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldRaiseErrorInConstructor', '--phantomjs-instances', '1', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
results: {
run: 0,
failures: 0,
errors: 1,
skipped: 0
}
});
itRuns({
testCase: 'ignoreError',
exitCode: 0,
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldRaiseError', '--phantomjs-instances', '1', '--ignore-errors', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
args: ['--config.resources./', atTestsRoot, '--config.resources./', atFrameworkPath, '--config.tests.aria-templates.classpaths.includes', 'test.attester.ShouldRaiseError', '--ignore-errors', '--config.coverage.files.rootDirectory', atTestsRoot, '--config.coverage.files.includes', '**/*.js'],
results: {

@@ -156,3 +201,3 @@ run: 1,

exitCode: 0,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/sample-tests/**/*.js', '--config.tests.mocha.files.excludes', '**/syntaxError*', '--config.tests.mocha.files.excludes', '**/*.txt', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/sample-tests/**/*.js', '--config.tests.mocha.files.excludes', '**/syntaxError*', '--config.tests.mocha.files.excludes', '**/*.txt'],
results: {

@@ -169,3 +214,3 @@ run: 2,

exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/bdd.js', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/bdd.js'],
results: {

@@ -182,3 +227,3 @@ run: 1,

exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/qunit.js', '--config.tests.mocha.ui', 'qunit', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/qunit.js', '--config.tests.mocha.ui', 'qunit'],
results: {

@@ -195,3 +240,3 @@ run: 1,

exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/tdd.js', '--config.tests.mocha.ui', 'tdd', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/tdd.js', '--config.tests.mocha.ui', 'tdd'],
results: {

@@ -208,3 +253,3 @@ run: 1,

exitCode: 0,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/chai/bdd.js', '--config.tests.mocha.assertion', 'chai', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/chai/bdd.js', '--config.tests.mocha.assertion', 'chai'],
results: {

@@ -221,3 +266,3 @@ run: 2,

exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/globals.js', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/globals.js'],
results: {

@@ -234,3 +279,3 @@ run: 1,

exitCode: 0,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/globals.js', '--config.tests.mocha.ignoreLeaks', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/globals.js', '--config.tests.mocha.ignoreLeaks'],
results: {

@@ -247,3 +292,3 @@ run: 1,

exitCode: 0,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/globals.js', '--config.tests.mocha.globals', 'globalOne', '--config.tests.mocha.globals', 'globalTwo', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/globals.js', '--config.tests.mocha.globals', 'globalOne', '--config.tests.mocha.globals', 'globalTwo'],
results: {

@@ -260,3 +305,3 @@ run: 1,

exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/fixtures.js', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/fixtures.js'],
results: {

@@ -274,3 +319,3 @@ run: 0,

exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/globalErrors.js', '--phantomjs-instances', '1'],
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/expect/globalErrors.js'],
results: {

@@ -283,2 +328,68 @@ run: 0,

});
itRuns({
testCase: 'mocha with external scripts',
exitCode: 0,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/extraScripts/actualTest.js', '--config.resources./', 'spec/test-type/mocha/extraScripts', '--config.tests.mocha.extraScripts', '/require_one.js', '--config.tests.mocha.extraScripts', '/require_two.js'],
results: {
run: 1,
failures: 0,
errors: 0,
skipped: 0
}
});
itRuns({
testCase: 'test timeout',
exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/extraScripts/timeout.js', '--task-timeout', '200'],
results: {
run: 1,
failures: 0,
errors: 1,
skipped: 0
},
hasErrors: ["Task timeout."]
});
itRuns({
testCase: 'browser disconnected',
exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/extraScripts/disconnect.js'],
results: {
run: 1,
failures: 0,
errors: 1,
skipped: 0
},
hasErrors: ["Browser was disconnected"]
});
// There are 3 tests lasting ~500ms, with a timeout of 1000ms everything should be fine
itRuns({
testCase: 'clear timeout',
exitCode: 0,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/slowTests/*.js', '--task-timeout', '1000'],
results: {
run: 3,
failures: 0,
errors: 0,
skipped: 0
}
});
// There are 3 tests lasting ~500ms, with a timeout of 200ms all of them should fail
itRuns({
testCase: 'timeout more tests',
exitCode: 1,
args: ['--config.tests.mocha.files.includes', 'spec/test-type/mocha/slowTests/*.js', '--task-timeout', '200'],
results: {
run: 3,
failures: 0,
errors: 3,
skipped: 0
},
// Expecting 3 errors
hasErrors: ["Task timeout.", "Task timeout.", "Task timeout."]
});
});

@@ -0,1 +1,2 @@

/* globals expect, describe, it */
/*

@@ -16,5 +17,7 @@ * Copyright 2012 Amadeus s.a.s.

var config = require("../../lib/config");
var path = require("path");
var fs = require("fs");
var config = require("../../lib/config");
var expectedObject = {

@@ -100,2 +103,58 @@ resources: {

});
it("should read yml files with templates", function () {
var configPath = path.join(__dirname, "configurations/template.yml");
var envPath = path.join(__dirname, "configurations/env.yml");
var read = config.readConfig(configPath, null, envPath);
var packageJson = fs.readFileSync("package.json");
expect(read.resources["/"]).toEqual(["here", "there"]);
expect(read.tests["aria-templates"].bootstrap).toEqual('/aria/aria-templates-1.2.3.js');
expect(read.env).toEqual({
version: "1.2.3",
name: "attester"
});
// test also missing properties
expect(read.paths.notReplacing).toEqual("<%= missing %>");
});
it("should read json files with templates", function () {
var configPath = path.join(__dirname, "configurations/template.json");
var envPath = path.join(__dirname, "../../package.json");
var read = config.readConfig(configPath, null, envPath);
var packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json")));
expect(read.resources["/"]).toEqual(["here", "there"]);
expect(read.tests["aria-templates"].bootstrap).toEqual('/aria/aria-templates-' + packageJson.version + '.js');
expect(read.env).toEqual(packageJson);
// test also missing properties
expect(read.paths.notReplacing).toEqual("<%= missing %>");
});
it("should read files with nested references", function () {
var configPath = path.join(__dirname, "configurations/nested.yml");
var read = config.readConfig(configPath);
expect(read.one).toEqual("abcde");
expect(read.two).toEqual("bcde");
expect(read.three).toEqual("cde");
expect(read.four).toEqual("d");
expect(read.another).toEqual("de");
expect(read.full).toEqual("abcde");
});
it("should fail nicely with circular references", function () {
var configPath = path.join(__dirname, "configurations/circular.yml");
var read = config.readConfig(configPath);
expect(read.first).toEqual("<%= second %>");
expect(read.second).toEqual("<%= third %>");
expect(read.third).toEqual("<%= first %>");
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc