diff --git a/.travis.yml b/.travis.yml index 3bf526f..b29d5f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,18 @@ language: node_js sudo: false node_js: - - "0.12.4" + - "5.8" + - "0.12" +env: + - CXX=g++-4.8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 before_install: - - "npm install -g npm" - - "npm install -g grunt-cli" + - "npm install -g npm" + - "npm install -g grunt-cli" install: - - "npm install" \ No newline at end of file + - "npm install" \ No newline at end of file diff --git a/bin/cli.js b/bin/cli.js index 54cfbe8..723e385 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -3,7 +3,7 @@ var debug = require('debug')('ylt:cli'); var meow = require('meow'); var path = require('path'); -var jstoxml = require('jstoxml'); +var EasyXml = require('easyxml'); var ylt = require('../lib/index'); @@ -15,7 +15,6 @@ var cli = meow({ 'Options:', ' --device Use "phone" or "tablet" to simulate a mobile device (by user-agent and viewport size).', ' --screenshot Will take a screenshot and use this value as the output path. It needs to end with ".png".', - ' --js-deep-analysis When activated, the javascriptExecutionTree will contain sub-requests.', ' --wait-for-selector Once the page is loaded, Phantomas will wait until the given CSS selector matches some elements.', ' --cookie Adds a cookie on the main domain.', ' --auth-user Basic HTTP authentication username.', @@ -51,11 +50,6 @@ if (screenshot) { options.screenshot = cli.flags.screenshot; } -// Deep JS analysis option -if (cli.flags.jsDeepAnalysis === true || cli.flags.jsDeepAnalysis === 'true') { - options.jsDeepAnalysis = true; -} - // Device simulation options.device = cli.flags.device || 'desktop'; @@ -86,7 +80,8 @@ if (cli.flags.reporter && cli.flags.reporter !== 'json' && cli.flags.reporter != debug('Success'); switch(cli.flags.reporter) { case 'xml': - console.log(jstoxml.toXML(data, {indent: ' '})); + var serializer = new EasyXml(); + console.log(serializer.render(data)); break; default: console.log(JSON.stringify(data, null, 2)); diff --git a/front/src/css/index.css b/front/src/css/index.css index fa05717..8bcc1ff 100644 --- a/front/src/css/index.css +++ b/front/src/css/index.css @@ -34,7 +34,8 @@ font-size: 1em; } .settings input[type=text], -.settings input[type=password] { +.settings input[type=password], +.settings textarea { width: 100%; min-width: 4em; } diff --git a/front/src/js/services/apiService.js b/front/src/js/services/apiService.js index 0482748..ba2680b 100644 --- a/front/src/js/services/apiService.js +++ b/front/src/js/services/apiService.js @@ -9,25 +9,26 @@ apiService.factory('API', ['$location', 'Runs', 'Results', function($location, R url: url, waitForResponse: false, screenshot: true, - jsTimeline: true, device: settings.device, waitForSelector: settings.waitForSelector, cookie: settings.cookie, authUser: settings.authUser, - authPass: settings.authPass + authPass: settings.authPass, + blockDomain: settings.blockDomain, + allowedDomains: settings.allowedDomains, + noExternals: settings.noExternals }; - if (settings.waitForSelector && settings.waitForSelector !== '') { - runObject.waitForSelector = settings.waitForSelector; - } - - if (settings.cookie && settings.cookie !== '') { - runObject.cookie = settings.cookie; - } - - if (settings.authUser && settings.authUser !== '' && settings.authPass && settings.authPass !== '') { - runObject.authUser = settings.authUser; - runObject.authPass = settings.authPass; + + if (settings.domainsBlackOrWhite === 'black') { + runObject.blockDomain = this.parseDomains(settings.domains); + } else if (settings.domainsBlackOrWhite === 'white') { + var allowedDomains = this.parseDomains(settings.domains); + if (allowedDomains.length > 0) { + runObject.allowDomain = allowedDomains; + } else { + runObject.noExternals = true; + } } Runs.save(runObject, function(data) { @@ -43,6 +44,17 @@ apiService.factory('API', ['$location', 'Runs', 'Results', function($location, R relaunchTest: function(result) { this.launchTest(result.params.url, result.params.options); + }, + + parseDomains: function(textareaContent) { + var lines = textareaContent.split('\n'); + + function removeEmptyLines (line) { + return line.trim() !== ''; + } + + // Remove empty lines + return lines.filter(removeEmptyLines).join(','); } }; diff --git a/front/src/less/index.less b/front/src/less/index.less index 5c665b4..bbc8b4d 100644 --- a/front/src/less/index.less +++ b/front/src/less/index.less @@ -38,7 +38,7 @@ font-size: 1em; } - input[type=text], input[type=password] { + input[type=text], input[type=password], textarea { width: 100%; min-width: 4em; } diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index f5dfa2c..430af91 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -1,6 +1,13 @@
+
+ : +
+ {{request}} +
+
+

Global score

diff --git a/front/src/views/index.html b/front/src/views/index.html index ada6eca..a72bb2a 100644 --- a/front/src/views/index.html +++ b/front/src/views/index.html @@ -61,15 +61,22 @@
- + diff --git a/front/src/views/rule.html b/front/src/views/rule.html index 39bb18b..d6a83e8 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -96,6 +96,12 @@ +
+ inline CSS + + : +
+
{{offender.color1}}
{{offender.color2}}
@@ -369,6 +375,18 @@ +
+

Protocols advertised by the server

+
+
+
{{protocol}}
+
+
+
none
+
+
+
+

404

Rule "{{policyName}}"" not found diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index 514318a..9c7b9e2 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -267,7 +267,7 @@ var policies = { }, "jQueryVersion": { "label": "jQuery version", - "message": "

Current latest versions of jQuery are 1.11 (with support for old IE versions) and 2.1 (without).

Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.

", + "message": "

Current latest versions of jQuery are 1.12 (with support for old IE versions) and 2.2 (without).

Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.

", "hasOffenders": false, "scoreFn": function(data) { var differentVersions = data.toolsResults.phantomas.metrics.jQueryVersionsLoaded; @@ -279,27 +279,31 @@ var policies = { var value = data.toolsResults.phantomas.metrics.jQueryVersion; var score; - if (value.indexOf('1.11.') === 0 || - value.indexOf('1.12.') === 0 || - value.indexOf('2.1.') === 0 || + if (value.indexOf('1.12.') === 0 || value.indexOf('2.2.') === 0 || - value.indexOf('3.0.') === 0) { + value.indexOf('1.13.') === 0 || + value.indexOf('2.3.') === 0 || + value.indexOf('3.0.') === 0 || + value.indexOf('3.1.') === 0) { score = 100; + } else if (value.indexOf('1.11.') === 0 || + value.indexOf('2.1.') === 0) { + score = 90; } else if (value.indexOf('1.10.') === 0 || value.indexOf('2.0.') === 0) { - score = 90; - } else if (value.indexOf('1.9.') === 0) { score = 70; - } else if (value.indexOf('1.8.') === 0) { + } else if (value.indexOf('1.9.') === 0) { score = 50; - } else if (value.indexOf('1.7') === 0) { + } else if (value.indexOf('1.8.') === 0) { score = 40; - } else if (value.indexOf('1.6') === 0) { + } else if (value.indexOf('1.7') === 0) { score = 30; - } else if (value.indexOf('1.5') === 0) { + } else if (value.indexOf('1.6') === 0) { score = 20; - } else if (value.indexOf('1.4') === 0) { + } else if (value.indexOf('1.5') === 0) { score = 10; + } else if (value.indexOf('1.4') === 0) { + score = 0; } else if (value.indexOf('1.3') === 0) { score = 0; } else if (value.indexOf('1.2') === 0) { @@ -420,7 +424,40 @@ var policies = { "isOkThreshold": 750, "isBadThreshold": 3000, "isAbnormalThreshold": 4500, - "hasOffenders": false + "hasOffenders": true, + "offendersTransformFn": function(offenders) { + var hasInline = false; + var inlineCount = 0; + var files = []; + + offenders.forEach(function(line) { + if (line.indexOf('[inline CSS]: ') === 0) { + hasInline = true; + inlineCount += parseInt(line.substr(14)); + } else { + var parts = /^<(.*)>: (\d+)$/.exec(line); + + if (parts) { + files.push({ + file: parts[1], + rules: parseInt(parts[2], 10) + }); + } + } + }); + + if (hasInline) { + files.push({ + file: 'inline CSS', + rules: inlineCount + }); + } + + return { + count: files.length, + list: files + }; + } }, "cssComplexSelectors": { "tool": "phantomas", @@ -925,7 +962,7 @@ var policies = { "imageOptimization": { "tool": "weightChecker", "label": "Image optimization", - "message": "

This metric mesures the number of bytes that could be saved by optimizing images.

Image optimization is generally one of the easiest way to reduce a page weight, and as a result, the page load time. Don't use Photoshop or other image editing tools, they're not very good for optimization. Use specialized tools such as Kraken.io or the excellent ImageOptim on Mac. For SVG images, you can use SVGOMG

The tools in use in YellowLabTools are not set to their maximum optimization power (JPEG quality 85), so you might be able to compress even more!

Please note that Yellow Lab Tools' engine (PhantomJS) is not compatible with image srcset (unless you use a polyfill). This can lead to incorrect page weight.

", + "message": "

This metric measures the number of bytes that could be saved by optimizing images.

Image optimization is generally one of the easiest way to reduce a page weight, and as a result, the page load time. Don't use Photoshop or other image editing tools, they're not very good for optimization. Use specialized tools such as Kraken.io or the excellent ImageOptim on Mac. For SVG images, you can use SVGOMG

The tools in use in YellowLabTools are not set to their maximum optimization power (JPEG quality 85), so you might be able to compress even more!

Please note that Yellow Lab Tools' engine (PhantomJS) is not compatible with image srcset (unless you use a polyfill). This can lead to incorrect page weight.

", "isOkThreshold": 10240, "isBadThreshold": 122880, "isAbnormalThreshold": 307200, @@ -935,7 +972,7 @@ var policies = { "gzipCompression": { "tool": "weightChecker", "label": "Gzip compression", - "message": "

Mesures the number of bytes that could be saved by compressing file transfers.

Gzip is a powerfull weight reducer and should be enabled on text-based assets in your server's configuration. Note that gzipping small files (< 1 KB) is arguable, and that some assets such as images should not be gzipped as they are already compressed. Here is a list of Content-Types that should be gzipped.

", + "message": "

Measures the number of bytes that could be saved by compressing file transfers.

Gzip is a powerfull weight reducer and should be enabled on text-based assets in your server's configuration. Note that gzipping small files (< 1 KB) is arguable, and that some assets such as images should not be gzipped as they are already compressed. Here is a list of Content-Types that should be gzipped.

", "isOkThreshold": 5125, "isBadThreshold": 81920, "isAbnormalThreshold": 153600, @@ -1044,6 +1081,35 @@ var policies = { "isAbnormalThreshold": 30, "hasOffenders": true }, + "http2": { + "label": "HTTP/2 or SPDY", + "message": "

HTTP/2 is the latest version of the HTTP protocol and is designed to optimize load speed. SPDY is deprecated but still very well supported.

The latest versions of all major browsers are now compatible. The difficulty is on the server side, where technologies are not quite ready yet.

", + "hasOffenders": true, + "scoreFn": function(data) { + if (!data.toolsResults.http2) { + return null; + } + + var isHttp2 = data.toolsResults.http2.metrics.http2; + + var result = { + value: isHttp2 ? 'Yes' : 'No', + score: isHttp2 ? 100 : 0, + bad: !isHttp2, + abnormal: false, + abnormalityScore: 0 + }; + + if (data.toolsResults.http2.offenders) { + result.offendersObj = { + count: data.toolsResults.http2.offenders.http2.length, + list: data.toolsResults.http2.offenders.http2 + }; + } + + return result; + } + }, "cachingDisabled": { "tool": "phantomas", "label": "Caching disabled", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 9c2f68f..a65a931 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -96,6 +96,7 @@ "serverConfig": { "label": "Server config", "policies": { + "http2": 2, "closedConnections": 2, "cachingNotSpecified": 1, "cachingDisabled": 1, diff --git a/lib/runner.js b/lib/runner.js index e5fa3c7..4c5daf2 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -5,6 +5,7 @@ var phantomasWrapper = require('./tools/phantomas/phantomasWrapper'); var jsExecutionTransformer = require('./tools/jsExecutionTransformer'); var colorDiff = require('./tools/colorDiff'); var mediaQueriesChecker = require('./tools/mediaQueriesChecker'); +var isHttp2 = require('./tools/isHttp2'); var weightChecker = require('./tools/weightChecker/weightChecker'); var rulesChecker = require('./rulesChecker'); var scoreCalculator = require('./scoreCalculator'); @@ -41,6 +42,11 @@ var Runner = function(params) { }) + .then(function(data) { + // Check if HTTP2 + return isHttp2.check(data); + }) + .then(function(data) { // Rules checker @@ -60,9 +66,14 @@ var Runner = function(params) { delete data.toolsResults.phantomas.metrics.scrollExecutionTree; delete data.toolsResults.phantomas.offenders.scrollExecutionTree; + + if (data.toolsResults.phantomas.offenders.blockedRequests) { + data.blockedRequests = data.toolsResults.phantomas.offenders.blockedRequests; + } + + // Finished! deferred.resolve(data); - }) .fail(function(err) { diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js index 56d377b..952932d 100644 --- a/lib/server/controllers/apiController.js +++ b/lib/server/controllers/apiController.js @@ -34,12 +34,14 @@ var ApiController = function(app) { waitForResponse: req.body.waitForResponse !== false && req.body.waitForResponse !== 'false' && req.body.waitForResponse !== 0, partialResult: req.body.partialResult || null, screenshot: req.body.screenshot || false, - jsTimeline: req.body.jsTimeline || false, device: req.body.device || 'desktop', waitForSelector: req.body.waitForSelector || null, cookie: req.body.cookie || null, authUser: req.body.authUser || null, - authPass: req.body.authPass || null + authPass: req.body.authPass || null, + blockDomain: req.body.blockDomain || null, + allowDomain: req.body.allowDomain || null, + noExternals: req.body.noExternals || false } }; @@ -71,12 +73,14 @@ var ApiController = function(app) { var runOptions = { screenshot: run.params.screenshot ? screenshot.getTmpFilePath() : false, - jsDeepAnalysis: run.params.jsTimeline, device: run.params.device, waitForSelector: run.params.waitForSelector, cookie: run.params.cookie, authUser: run.params.authUser, authPass: run.params.authPass, + blockDomain: run.params.blockDomain, + allowDomain: run.params.allowDomain, + noExternals: run.params.noExternals, phantomasEngine: serverSettings.phantomasEngine }; @@ -96,7 +100,7 @@ var ApiController = function(app) { if (run.params.screenshot) { // Replace the empty promise created earlier with Q.resolve() - screenshotPromise = screenshot.toThumbnail(400) + screenshotPromise = screenshot.toThumbnail(serverSettings.screenshotWidth || 400) // Read screenshot .then(function(screenshotBuffer) { @@ -129,13 +133,7 @@ var ApiController = function(app) { // Remove uneeded temp screenshot path delete data.params.options.screenshot; - // Empty javascriptExecutionTree if not needed - if (!run.params.jsTimeline) { - data.javascriptExecutionTree = {}; - data.scrollExecutionTree = {}; - } - - // Remove tools results if not needed + // Here we can remove tools results if not needed return resultsDatastore.saveResult(data); }) diff --git a/lib/tools/isHttp2.js b/lib/tools/isHttp2.js new file mode 100644 index 0000000..acea838 --- /dev/null +++ b/lib/tools/isHttp2.js @@ -0,0 +1,105 @@ +var debug = require('debug')('ylt:isHttp2'); +var url = require('url'); +var Q = require('q'); +var http2 = require('is-http2'); + +var isHttp2 = function() { + 'use strict'; + + this.check = function(data) { + debug('Starting to check for HTTP2 support...'); + + return this.checkHttp2(data) + + .then(function(result) { + + if (result.isHttp) { + debug('The website is not even in HTTPS'); + + data.toolsResults.http2 = { + metrics: { + http2: false + } + }; + + } else if (result.isHttp2) { + debug('HTTP/2 (or SPDY) is supported'); + + data.toolsResults.http2 = { + metrics: { + http2: true + } + }; + + } else { + debug('HTTP/2 is not supported'); + + data.toolsResults.http2 = { + metrics: { + http2: false + } + }; + } + + // Add the supported protocols as offenders + if (result.supportedProtocols) { + debug('Supported protocols: ' + result.supportedProtocols.join(' ')); + data.toolsResults.http2.offenders = { + http2: result.supportedProtocols + }; + } + + debug('End of HTTP2 support check'); + + return data; + }) + + .fail(function() { + return data; + }); + }; + + this.getParsedUrl = function(data) { + return url.parse(data.toolsResults.phantomas.url); + }; + + this.getProtocol = function(data) { + return this.getParsedUrl(data).protocol; + }; + + this.getDomain = function(data) { + return this.getParsedUrl(data).hostname; + }; + + this.checkHttp2 = function(data) { + var deferred = Q.defer(); + + // Check if it's HTTPS first + if (this.getProtocol(data) === 'http:') { + + deferred.resolve({ + isHttp: true + }); + + } else { + + // To make is-http2 work, you need to have openssl in a version greater than 1.0.0 installed and available in your $path. + http2(this.getDomain(data), {includeSpdy: true}) + + .then(function(result) { + deferred.resolve(result); + }) + + .catch(function(error) { + debug('Error while checking HTTP2 support:'); + debug(error); + deferred.reject('Error while checking for HTTP2 support'); + }); + + } + + return deferred.promise; + }; +}; + +module.exports = new isHttp2(); \ No newline at end of file diff --git a/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js index 5bd04d3..aa0aaa7 100644 --- a/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js +++ b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js @@ -18,8 +18,8 @@ exports.module = function(phantomas) { responseEndTime = Date.now(); }); - phantomas.once('init', function() { - phantomas.evaluate(function(responseEndTime, deepAnalysis) { + phantomas.on('init', function() { + phantomas.evaluate(function(responseEndTime) { (function(phantomas) { // Overwritting phantomas spy function @@ -106,9 +106,6 @@ exports.module = function(phantomas) { var currentContext = root; var depth = 0; - if (deepAnalysis) { - phantomas.log('Entering deep Javascript analysis mode'); - } // Add a child but don't enter its context function pushContext(data) { @@ -129,9 +126,7 @@ exports.module = function(phantomas) { } } - if (depth === 0 || deepAnalysis) { - currentContext.addChild(data); - } + currentContext.addChild(data); } // Add a child to the current context and enter its context @@ -153,9 +148,7 @@ exports.module = function(phantomas) { } } - if (depth === 0 || deepAnalysis) { - currentContext = currentContext.addChild(data); - } + currentContext = currentContext.addChild(data); depth ++; } @@ -178,21 +171,18 @@ exports.module = function(phantomas) { } } - if (depth === 1 || deepAnalysis) { - - // Merge previous data with moreData (ovewrites if exists) - if (moreData) { - for (var key in moreData) { - currentContext.data[key] = moreData[key]; - } + // Merge previous data with moreData (ovewrites if exists) + if (moreData) { + for (var key in moreData) { + currentContext.data[key] = moreData[key]; } + } - var parent = currentContext.parent; - if (parent === null) { - console.error('Error: trying to close root context in ContextTree'); - } else { - currentContext = parent; - } + var parent = currentContext.parent; + if (parent === null) { + console.error('Error: trying to close root context in ContextTree'); + } else { + currentContext = parent; } depth --; @@ -255,6 +245,6 @@ exports.module = function(phantomas) { })(); })(window.__phantomas); - }, responseEndTime, phantomas.getParam('js-deep-analysis')); + }, responseEndTime); }); }; diff --git a/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js index 4d71dac..be07140 100644 --- a/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js @@ -19,7 +19,7 @@ exports.module = function(phantomas) { phantomas.setMetric('DOMqueriesAvoidable'); // @desc number of repeated uses of a duplicated query // fake native DOM functions - phantomas.once('init', function() { + phantomas.on('init', function() { phantomas.evaluate(function() { (function(phantomas) { function querySpy(type, query, fnName, context, hasNoResults) { diff --git a/lib/tools/phantomas/custom_modules/modules/eventYLT/eventYLT.js b/lib/tools/phantomas/custom_modules/modules/eventYLT/eventYLT.js index 82b678e..fd95e74 100644 --- a/lib/tools/phantomas/custom_modules/modules/eventYLT/eventYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/eventYLT/eventYLT.js @@ -12,7 +12,7 @@ exports.module = function(phantomas) { phantomas.setMetric('eventsDispatched'); // @desc number of EventTarget.dispatchEvent calls phantomas.setMetric('eventsScrollBound'); // @desc number of scroll event bounds - phantomas.once('init', function() { + phantomas.on('init', function() { phantomas.evaluate(function() { (function(phantomas) { // spy calls to EventTarget.addEventListener diff --git a/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js index 032f21e..faf3042 100644 --- a/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js @@ -138,7 +138,7 @@ exports.module = function(phantomas) { jQueryFunctions = jQueryFunctions.concat(jQueryTraversalFunctions); // spy calls to jQuery functions - phantomas.once('init', function() { + phantomas.on('init', function() { phantomas.evaluate(function(jQueryFunctions, jQueryTraversalFunctions) { (function(phantomas) { var oldJQuery; diff --git a/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js b/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js index b1b06ce..61489af 100644 --- a/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js @@ -23,7 +23,7 @@ exports.module = function(phantomas) { phantomas.log('javaScriptBottlenecks: to spy calls to eval() run phantomas with --spy-eval option'); } - phantomas.once('init', function() { + phantomas.on('init', function() { phantomas.evaluate(function(spyEval) { (function(phantomas) { function report(msg, caller, backtrace, metric) { diff --git a/lib/tools/phantomas/phantomasWrapper.js b/lib/tools/phantomas/phantomasWrapper.js index 7d51542..520013e 100644 --- a/lib/tools/phantomas/phantomasWrapper.js +++ b/lib/tools/phantomas/phantomasWrapper.js @@ -22,7 +22,6 @@ var PhantomasWrapper = function() { // Cusomizable options 'engine': task.options.phantomasEngine || 'webkit', 'timeout': task.options.timeout || 30, - 'js-deep-analysis': task.options.jsDeepAnalysis || false, 'user-agent': (task.options.device === 'desktop') ? 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) YLT Chrome/27.0.1453.110 Safari/537.36' : null, 'tablet': (task.options.device === 'tablet'), 'phone': (task.options.device === 'phone'), @@ -31,12 +30,14 @@ var PhantomasWrapper = function() { 'cookie': task.options.cookie, 'auth-user': task.options.authUser, 'auth-pass': task.options.authPass, + 'block-domain': task.options.blockDomain, + 'allow-domain': task.options.allowDomain, + 'no-externals': task.options.noExternals, // Mandatory 'reporter': 'json:pretty', 'analyze-css': true, 'skip-modules': [ - 'blockDomains', // not needed 'domHiddenContent', // overriden 'domMutations', // not compatible with webkit 'domQueries', // overriden @@ -118,11 +119,6 @@ var PhantomasWrapper = function() { async.retry(triesNumber, function(cb) { currentTry ++; - // Fix for https://github.com/gmetais/YellowLabTools/issues/114 - if (currentTry === 2 && options.engine === 'webkit2') { - debug('Launching a second try with the old webkit v1 engine'); - options.engine = 'webkit'; - } var process = phantomas(task.url, options, function(err, json, results) { var errorCode = err ? parseInt(err.message, 10) : null; @@ -155,6 +151,11 @@ var PhantomasWrapper = function() { } cb(errorCode, json); + + }).fail(function() { + // This function is useless, but the failing promise needs to be handled, + // otherwise the module meow writes in the console in case of a timeout (error code 252). + debug('Failing promise handled'); }); phantomasPid = process.pid; diff --git a/package.json b/package.json index c8a75c8..4c6e11e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yellowlabtools", - "version": "1.9.3", + "version": "1.10.0", "description": "Online tool to audit a webpage for performance and front-end quality issues", "license": "GPL-2.0", "author": { @@ -20,51 +20,52 @@ }, "main": "./lib/index.js", "dependencies": { - "angular": "1.4.8", - "angular-animate": "1.4.8", - "angular-chart.js": "0.8.8", - "angular-local-storage": "0.2.2", - "angular-resource": "1.4.8", - "angular-route": "1.4.8", - "angular-sanitize": "1.4.8", - "async": "1.5.0", - "body-parser": "1.14.2", + "angular": "1.5.2", + "angular-animate": "1.5.2", + "angular-chart.js": "0.9.0", + "angular-local-storage": "0.2.7", + "angular-resource": "1.5.2", + "angular-route": "1.5.2", + "angular-sanitize": "1.5.2", + "async": "1.5.2", + "body-parser": "1.15.0", "chart.js": "1.0.2", - "clean-css": "3.4.8", - "color-diff": "0.1.7", - "compression": "1.6.0", + "clean-css": "3.4.10", + "color-diff": "1.0.0", + "compression": "1.6.1", "cors": "2.7.1", "css-mq-parser": "0.0.3", "debug": "2.2.0", - "express": "4.13.3", + "easyxml": "2.0.1", + "express": "4.13.4", "imagemin": "4.0.0", "imagemin-jpegoptim": "4.1.0", - "jstoxml": "0.2.3", + "is-http2": "1.0.4", "lwip": "0.0.8", - "meow": "3.6.0", - "minimize": "1.7.4", + "meow": "3.7.0", + "minimize": "1.8.1", "parse-color": "1.0.0", - "phantomas": "1.13.0", + "phantomas": "1.15.1", "ps-node": "0.0.5", "q": "1.4.1", - "request": "2.67.0", - "rimraf": "2.4.4", + "request": "2.69.0", + "rimraf": "2.5.2", "temporary": "0.0.8", "try-thread-sleep": "1.0.0", - "uglify-js": "2.6.1" + "uglify-js": "2.6.2" }, "devDependencies": { - "chai": "~3.4.0", + "chai": "~3.5.0", "grunt": "~0.4.5", "grunt-blanket": "~0.0.10", - "grunt-contrib-clean": "~0.7.0", - "grunt-contrib-concat": "~0.5.1", - "grunt-contrib-copy": "~0.8.2", - "grunt-contrib-cssmin": "~0.14.0", - "grunt-contrib-htmlmin": "~0.6.0", - "grunt-contrib-jshint": "~0.11.3", - "grunt-contrib-less": "~1.1.0", - "grunt-contrib-uglify": "~0.11.0", + "grunt-contrib-clean": "~1.0.0", + "grunt-contrib-concat": "~1.0.0", + "grunt-contrib-copy": "~1.0.0", + "grunt-contrib-cssmin": "~1.0.1", + "grunt-contrib-htmlmin": "~1.1.0", + "grunt-contrib-jshint": "~1.0.0", + "grunt-contrib-less": "~1.2.0", + "grunt-contrib-uglify": "~1.0.1", "grunt-env": "~0.4.4", "grunt-express": "~1.4.1", "grunt-filerev": "~2.3.1", @@ -73,10 +74,10 @@ "grunt-mocha-test": "~0.12.7", "grunt-replace": "~0.11.0", "grunt-usemin": "~3.1.1", - "grunt-webfont": "~1.1.0", - "matchdep": "~1.0.0", - "mocha": "~2.3.2", - "sinon": "~1.17.2", + "grunt-webfont": "~1.2.0", + "matchdep": "~1.0.1", + "mocha": "~2.4.5", + "sinon": "~1.17.3", "sinon-chai": "~2.8.0" }, "scripts": { diff --git a/server_config/settings-prod.json b/server_config/settings-prod.json index cddfdae..b3d890d 100644 --- a/server_config/settings-prod.json +++ b/server_config/settings-prod.json @@ -2,6 +2,7 @@ "serverPort": 80, "phantomasEngine": "webkit", "googleAnalyticsId": "", + "screenshotWidth": 400, "authorizedKeys": { diff --git a/server_config/settings.json b/server_config/settings.json index dcd2136..6b7bc71 100644 --- a/server_config/settings.json +++ b/server_config/settings.json @@ -2,6 +2,7 @@ "serverPort": 8383, "phantomasEngine": "webkit", "googleAnalyticsId": "", + "screenshotWidth": 400, "authorizedKeys": { diff --git a/test/api/apiTest.js b/test/api/apiTest.js index c13701f..c4fb064 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -31,6 +31,7 @@ describe('api', function() { }, json: true, headers: { + 'Content-Type': 'application/json', 'X-Api-Key': 'invalid' } }, function(error, response, body) { @@ -53,6 +54,7 @@ describe('api', function() { }, json: true, headers: { + 'Content-Type': 'application/json', 'X-Api-Key': Object.keys(config.authorizedKeys)[0] } }, function(error, response, body) { @@ -76,6 +78,7 @@ describe('api', function() { }, json: true, headers: { + 'Content-Type': 'application/json', 'X-Api-Key': Object.keys(config.authorizedKeys)[0] } }, function(error, response, body) { @@ -99,12 +102,13 @@ describe('api', function() { screenshot: true, device: 'tablet', //waitForSelector: '*', - cookie: 'foo=bar', + cookie: 'foo=bar;domain=google.com', authUser: 'joe', authPass: 'secret' }, json: true, headers: { + 'Content-Type': 'application/json', 'X-Api-Key': Object.keys(config.authorizedKeys)[0] } }, function(error, response, body) { @@ -133,6 +137,7 @@ describe('api', function() { }, json: true, headers: { + 'Content-Type': 'application/json', 'X-Api-Key': Object.keys(config.authorizedKeys)[0] } }, function(error, response, body) { @@ -165,14 +170,13 @@ describe('api', function() { body.should.have.a.property('rules').that.is.an('object'); body.should.have.a.property('toolsResults').that.is.an('object'); - // javascriptExecutionTree should only be filled if option jsTimeline is true body.should.have.a.property('javascriptExecutionTree').that.is.an('object'); - body.javascriptExecutionTree.should.deep.equal({}); + body.javascriptExecutionTree.should.not.deep.equal({}); // Check if settings are correctly sent and retrieved body.params.options.should.have.a.property('device').that.equals('tablet'); //body.params.options.should.have.a.property('waitForSelector').that.equals('*'); - body.params.options.should.have.a.property('cookie').that.equals('foo=bar'); + body.params.options.should.have.a.property('cookie').that.equals('foo=bar;domain=google.com'); body.params.options.should.have.a.property('authUser').that.equals('joe'); body.params.options.should.have.a.property('authPass').that.equals('secret'); @@ -208,6 +212,7 @@ describe('api', function() { }, json: true, headers: { + 'Content-Type': 'application/json', 'X-Api-Key': Object.keys(config.authorizedKeys)[0] } }, function(error, response, body) { @@ -232,6 +237,7 @@ describe('api', function() { url: serverUrl + '/api/runs/' + asyncRunId, json: true, headers: { + 'Content-Type': 'application/json', 'X-Api-Key': Object.keys(config.authorizedKeys)[0] } }, function(error, response, body) { diff --git a/test/core/customPoliciesTest.js b/test/core/customPoliciesTest.js index 784bf64..2f517cd 100644 --- a/test/core/customPoliciesTest.js +++ b/test/core/customPoliciesTest.js @@ -183,18 +183,19 @@ describe('customPolicies', function() { var versions = { '1.2.9': 0, - '1.3.9': 0, - '1.4.4': 10, - '1.5.0': 20, - '1.6.3': 30, - '1.7.0': 40, - '1.8.3a': 50, - '1.9.2': 70, - '1.10.1': 90, - '2.0.0-rc1': 90, - '1.11.1': 100, - '2.1.1-beta1': 100, - '3.0.0': 100 + '1.4.4': 0, + '1.5.0': 10, + '1.6.3': 20, + '1.7.0': 30, + '1.8.3a': 40, + '1.9.2': 50, + '1.10.1': 70, + '2.0.0-rc1': 70, + '1.11.1': 90, + '2.1.1-beta1': 90, + '1.12.1': 100, + '2.3.1': 100, + '3.1.0': 100 }; for (var version in versions) { diff --git a/test/core/isHttp2Test.js b/test/core/isHttp2Test.js new file mode 100644 index 0000000..1ace12e --- /dev/null +++ b/test/core/isHttp2Test.js @@ -0,0 +1,48 @@ +var should = require('chai').should(); +var isHttp2 = require('../../lib/tools/isHttp2'); + +describe('isHttp2', function() { + + it('should parse the protocol correctly', function() { + isHttp2.getProtocol({ + toolsResults: { + phantomas: { + url: 'http://www.yahoo.com' + } + } + }).should.equal('http:'); + + + isHttp2.getProtocol({ + toolsResults: { + phantomas: { + url: 'https://www.yahoo.com' + } + } + }).should.equal('https:'); + }); + + it('should parse the domain correctly', function() { + isHttp2.getDomain({ + toolsResults: { + phantomas: { + url: 'http://www.yahoo.com' + } + } + }).should.equal('www.yahoo.com'); + + + isHttp2.getDomain({ + toolsResults: { + phantomas: { + url: 'https://www.yahoo.com' + } + } + }).should.equal('www.yahoo.com'); + }); + + it('should have a function checkHttp2', function() { + isHttp2.should.have.a.property('checkHttp2').that.is.a('function'); + }); + +}); diff --git a/test/fixtures/settings.json b/test/fixtures/settings.json index 58c89ca..a68db4c 100644 --- a/test/fixtures/settings.json +++ b/test/fixtures/settings.json @@ -2,6 +2,7 @@ "serverPort": "8387", "phantomasEngine": "webkit", "googleAnalyticsId": "", + "screenshotWidth": 400, "authorizedKeys": { "1234567890": "contact@gaelmetais.com"