Compare commits

...
72 Commits
Author SHA1 Message Date
Gaël Métais 7e1bf0fc92 Merge pull request #112 from gmetais/develop
v1.8.0
2015-09-20 01:34:58 +02:00
Gaël Métais 82693a8587 v1.8 + update dependencies 2015-09-20 01:29:45 +02:00
Gaël Métais 99eace0336 Merge pull request #111 from gmetais/color-diff
New rule: similar colors
2015-09-20 01:19:13 +02:00
Gaël Métais d0257fea8f New rule: similar colors 2015-09-20 01:06:42 +02:00
Gaël Métais 453a5cda2e Stricter hiddenImages rule 2015-09-19 11:55:38 +02:00
Gaël Métais bdbd1a0f08 Merge pull request #108 from gmetais/hiddenImages
New metric: hiddenImages
2015-09-18 18:59:47 +02:00
Gaël Métais d32b9f78a4 New metric: hiddenImages 2015-09-18 18:51:41 +02:00
Gaël Métais 46c2d8c49e Fix backtraces in the profiler when using PhantomJS 2 2015-09-18 13:49:24 +02:00
Gaël Métais 88b5567131 Merge pull request #106 from gmetais/develop
v1.7.8
2015-09-15 07:53:41 +02:00
Gaël Métais 0e81d16feb Reduce the timeout from 45 to 30s 2015-09-15 07:37:33 +02:00
Gaël Métais a900f0ff65 Cooler DOMaccessesOnScroll rule 2015-09-14 22:54:17 +02:00
Gaël Métais d4faa75b79 Update dependencies 2015-09-14 22:52:50 +02:00
Gaël Métais 47b0de1b49 v1.7.8 2015-09-14 22:50:43 +02:00
Gaël Métais fe6a10592b Merge pull request #105 from gmetais/basic-auth
Basic auth
2015-09-14 22:49:29 +02:00
Gaël Métais 8f02349ca5 Fix tests 2015-09-14 22:36:43 +02:00
Gaël Métais 1fb32de428 Change auth pwd field to a password type 2015-09-14 17:25:23 +02:00
Gaël Métais 31b66ee6fa Send auth headers with requests 2015-09-14 17:25:23 +02:00
Gaël Métais d22f9b81b2 Merge pull request #104 from gmetais/develop
v1.7.7
2015-09-11 10:47:02 +02:00
Gaël Métais 3051d61365 Show the goal for 100/100 on the rule page 2015-09-11 10:24:20 +02:00
Gaël Métais 53fd2e48e5 Cooler on global variables, not on document.write 2015-09-11 10:23:13 +02:00
Gaël Métais ef516c123d Update dependencies 2015-09-11 08:45:01 +02:00
Gaël Métais add8962390 Merge pull request #103 from gmetais/xml
Add xml output option to the CLI
2015-09-10 22:58:57 +02:00
Gaël Métais 1a444a481e Add xml output option to the CLI 2015-09-10 18:42:19 +02:00
Gaël Métais 96ca4a7933 Remove input type url for mobiles, causing a bug on iOS 2015-09-10 18:08:05 +02:00
Gaël Métais f07bc615b0 Merge pull request #101 from gmetais/develop
v1.7.6
2015-08-28 11:18:43 +02:00
Gaël Métais 2d0c88390a Fix tests 2015-08-28 10:57:46 +02:00
Gaël Métais 52574623fe Merge branch 'phantomjs2-quick' into develop 2015-08-28 10:49:19 +02:00
Gaël Métais b1d95ddf5f v1.7.6 2015-08-28 10:48:11 +02:00
Gaël Métais 092bb285ed Add a phantomasEngine setting for the server 2015-08-28 10:36:49 +02:00
Gaël Métais 52ce26796a Merge pull request #98 from gmetais/develop
v1.7.5
2015-08-16 14:38:35 +02:00
Gaël Métais 6bd55e4807 Avoid destroying SVG fonts when optimizing images 2015-08-16 14:23:37 +02:00
Gaël Métais 8325a9297e v1.7.5 2015-08-16 14:04:29 +02:00
Gaël Métais 9cb3c78def Hide scrollbars on screenshot (show then on hover) 2015-08-16 14:03:55 +02:00
Gaël Métais 143436ecd3 Change tooltip message 2015-08-16 13:47:41 +02:00
Gaël Métais 83cf87c8cf Merge pull request #97 from gmetais/browserify
Remove Bower and update dependencies
2015-08-16 13:36:43 +02:00
Gaël Métais 2f1003e8bc Remove bower from shell scripts 2015-08-16 13:30:57 +02:00
Gaël Métais e0649eb9bd Replace bower by npm (browserify) 2015-08-16 13:22:12 +02:00
Gaël Métais d5c607d370 Update dependencies 2015-08-16 13:06:18 +02:00
Gaël Métais 2c2c8bf4cb Add author to package.json 2015-08-11 21:24:43 +02:00
Gaël Métais 08459ca289 Add keywords to package.json 2015-08-11 21:23:46 +02:00
Gaël Métais 162f1fa1d1 Migrate Travis-CI to container-base infra 2015-08-01 19:42:33 +02:00
Gaël Métais d9113edbbe Merge pull request #95 from gmetais/develop
v1.7.4
2015-08-01 19:41:26 +02:00
Gaël Métais eb1c5c07e9 v1.7.4 2015-08-01 19:30:56 +02:00
Gaël Métais 0574429a93 Warn about srcset not being used 2015-08-01 19:27:48 +02:00
Gaël Métais 8ff565404b Warn about srcset not being used 2015-08-01 19:22:06 +02:00
Gaël Métais b08d4b4cae Merge pull request #94 from gmetais/links
Change ng-click to real <a> on dashboard rows
2015-08-01 18:53:02 +02:00
Gaël Métais f331662263 Merge branch 'develop' of https://github.com/gmetais/YellowLabTools into develop 2015-08-01 18:52:06 +02:00
Gaël Métais 10693283c5 Add license to package.json 2015-08-01 18:51:51 +02:00
Gaël Métais f2a933c51b Remove uneeded function 2015-08-01 18:44:49 +02:00
Gaël Métais 172b8cfb9b Change ng-click to real links on rule rows 2015-07-31 19:41:47 +02:00
Gaël Métais 47738ffa26 Merge pull request #93 from gmetais/fix-update
Exit server update script if npm install fails
2015-07-31 16:39:17 +02:00
Gaël Métais c8a29b4651 Fail the shell scripts if npm or bower fail 2015-07-31 16:16:26 +02:00
Gaël Métais c305cc2f7d Merge branch 'develop' of https://github.com/gmetais/YellowLabTools into develop 2015-07-31 16:14:59 +02:00
Gaël Métais 01676e1f99 Fix typo 2015-07-30 11:09:34 +02:00
Gaël Métais de1e15fb25 Merge pull request #90 from RaphArbuz/fix-typo-deboucing
fix typo: deboucing -> debouncing
2015-07-22 16:41:23 +02:00
RaphArbuz 0aa77c33ad fix typo: deboucing -> debouncing 2015-07-22 16:37:05 +02:00
Gaël Métais 22eeb1a47a Merge pull request #89 from gmetais/develop
v1.7.3
2015-07-22 16:02:03 +02:00
Gaël Métais 15b3c6617b v1.7.3 2015-07-22 16:01:08 +02:00
Gaël Métais e342a88f7d Fix YLT freezing the queue when an incorrect gzip header is sent on a file 2015-07-22 15:48:45 +02:00
Gaël Métais 7779efedf1 Add a GitHub star button in the footer 2015-07-20 11:48:35 +02:00
Gaël Métais 137b4d25b9 Fix css offenders parsing when containing line breaks 2015-07-08 23:25:11 +02:00
Gaël Métais 9be40adabf v1.7.2 2015-06-24 08:42:00 +02:00
Gaël Métais b6d4b7b40d v1.7.2 2015-06-24 08:41:43 +02:00
Gaël Métais 3256789082 Merge pull request #87 from gmetais/fix-xml
Fix server freeze issue
2015-06-24 08:32:30 +02:00
Gaël Métais 87adc575ac Fix type xml issue 2015-06-24 08:01:58 +02:00
Gaël Métais 30ae0f93f3 Remove unneeded promise step 2015-06-24 08:00:56 +02:00
Gaël Métais c339654112 Failing test case with xml file 2015-06-24 07:49:44 +02:00
Gaël Métais 25c9770fba v1.7.1 2015-06-22 15:29:45 +02:00
Gaël Métais 0bf7326604 v1.7.1 2015-06-22 15:29:36 +02:00
Gaël Métais 7fc5a9ba8f v1.7.1 2015-06-22 15:25:47 +02:00
Gaël Métais 230f805cae Add libjpeg to installation script 2015-06-22 15:19:00 +02:00
Gaël Métais bc9c828584 Fix dependency problem 2015-06-22 15:18:19 +02:00
46 changed files with 657 additions and 164 deletions
-3
View File
@@ -1,3 +0,0 @@
{
"directory" : "bower_components"
}
-1
View File
@@ -1,5 +1,4 @@
node_modules
bower_components
.tmp
.vagrant
results/*
+1
View File
@@ -1,4 +1,5 @@
language: node_js
sudo: false
node_js:
- "0.12.4"
before_install:
+19 -5
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env node
var debug = require('debug')('ylt:cli');
var meow = require('meow');
var path = require('path');
var debug = require('debug')('ylt:cli');
var meow = require('meow');
var path = require('path');
var jstoxml = require('jstoxml');
var ylt = require('../lib/index');
var ylt = require('../lib/index');
var cli = meow({
help: [
@@ -19,6 +20,7 @@ var cli = meow({
' --cookie Adds a cookie on the main domain.',
' --auth-user Basic HTTP authentication username.',
' --auth-pass Basic HTTP authentication password.',
' --reporter The output format: "json" or "xml". Default is "json".',
''
].join('\n'),
pkg: '../package.json'
@@ -67,6 +69,12 @@ options.cookie = cli.flags.cookie || null;
options.authUser = cli.flags.authUser || null;
options.authPass = cli.flags.authPass || null;
// Output format
if (cli.flags.reporter && cli.flags.reporter !== 'json' && cli.flags.reporter !== 'xml') {
console.error('Incorrect parameters: reporter has to be "json" or "xml"');
process.exit(1);
}
(function execute(url, options) {
'use strict';
@@ -76,7 +84,13 @@ options.authPass = cli.flags.authPass || null;
then(function(data) {
debug('Success');
console.log(JSON.stringify(data, null, 2));
switch(cli.flags.reporter) {
case 'xml':
console.log(jstoxml.toXML(data, {indent: ' '}));
break;
default:
console.log(JSON.stringify(data, null, 2));
}
}).fail(function(err) {
-13
View File
@@ -1,13 +0,0 @@
{
"name": "yellowlabtools",
"dependencies": {
"angular": "1.3.15",
"angular-route": "1.3.15",
"angular-resource": "1.3.15",
"angular-sanitize": "1.3.15",
"angular-animate": "1.3.15",
"angular-local-storage": "0.2.2",
"angular-chart.js": "0.7.1",
"Chart.js": "~1.0.2"
}
}
+7 -3
View File
@@ -109,14 +109,18 @@
.summary .notations .criteria .table {
width: 100%;
}
.summary .notations .criteria .table > div:hover > div {
.summary .notations .criteria .table > a {
text-decoration: none;
color: inherit;
}
.summary .notations .criteria .table > a:hover > div {
background: #EBD8E2;
cursor: pointer;
}
.summary .notations .criteria .table > div:hover > div.info {
.summary .notations .criteria .table > a:hover > div.info {
background: #FFF;
}
.summary .notations .criteria .table > div:hover > div.info .icon-question {
.summary .notations .criteria .table > a:hover > div.info .icon-question {
color: #EBD8E2;
}
.summary .notations .criteria .grade {
+3 -1
View File
@@ -33,7 +33,8 @@
.settings select {
font-size: 1em;
}
.settings input[type=text] {
.settings input[type=text],
.settings input[type=password] {
width: 100%;
min-width: 4em;
}
@@ -128,6 +129,7 @@
.advanced > div > div.label {
width: 25%;
white-space: nowrap;
vertical-align: middle;
}
.advanced .subTable {
display: table;
+7 -7
View File
@@ -192,8 +192,11 @@ a.linkButton {
background: #000;
}
.screenshotWrapper > div {
overflow: scroll;
position: relative;
overflow: hidden;
}
.screenshotWrapper > div:hover {
overflow: auto;
}
.screenshotWrapper .screenshotImage {
width: 100%;
@@ -279,12 +282,6 @@ a.linkButton {
width: 8.25em;
height: 11em;
}
.star {
font-weight: bold;
}
.star span {
font-size: 1.2em;
}
.footer {
padding: 3em;
color: #413;
@@ -295,6 +292,9 @@ a.linkButton {
.footer .version {
font-size: 0.7em;
}
.footer .github {
margin: 1em 0 0 0.5em;
}
[class^="icon-"]:before,
[class*=" icon-"]:before {
vertical-align: baseline;
+23 -4
View File
@@ -29,9 +29,16 @@
border-radius: 0.5em;
margin: 0 auto 0.5em;
}
.rule h3 {
margin-bottom: 0em;
}
.rule .okThreshold {
font-style: italic;
font-size: 0.9em;
}
.rule .message {
width: 80%;
margin: 0 auto;
margin: 1.5em auto;
}
.rule .message p {
margin: 0.5em;
@@ -207,6 +214,17 @@
z-index: 2;
border: 0.2em solid #f1c40f;
}
.similarColors {
margin: 1em;
width: 20em;
height: 6em;
}
.similarColors > div {
display: inline-block;
width: 10em;
height: 3.5em;
padding-top: 2.5em;
}
.totalWeightPie {
max-width: 39em;
margin: 2em auto 4em;
@@ -237,8 +255,9 @@
margin-top: 0.5em;
}
.smallPreview {
max-height: 1.6em;
max-width: 4em;
display: block;
max-height: 4em;
max-width: 8em;
border: 1px solid #000;
margin-top: 0.2em;
margin: 1em auto 0.2em;
}
+4 -2
View File
@@ -152,14 +152,16 @@
width: 100%;
border-spacing: 0.25em;
}
.table > div {
.table > div,
.table > a {
display: table-row;
}
.table > .headers > div {
font-weight: bold;
padding: 0.5em 1em;
}
.table > div > div {
.table > div > div,
.table > a > div {
padding: 0.1em 1em;
background: #f2f2f2;
display: table-cell;
+5
View File
@@ -37,6 +37,11 @@ yltApp.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('$routeChangeSuccess', function(){
ga('send', 'pageview', {'page': $location.path()});
});
// GitHub star button (asynchronously loaded iframe)
window.addEventListener('load', function() {
window.document.getElementById('ghbtn').src = 'http://ghbtns.com/github-btn.html?user=gmetais&repo=YellowLabTools&type=star&count=true&size=large';
});
}]);
yltApp.config(['$routeProvider', '$locationProvider',
@@ -30,10 +30,6 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams
$scope.tweetText = 'I\'ve discovered this cool open-source tool that audits the front-end quality of a web page: ';
}
$scope.showRulePage = function(ruleName) {
$location.path('/result/' + $scope.runId + '/rule/' + ruleName);
};
$scope.testAgain = function() {
API.relaunchTest($scope.result);
};
+27 -3
View File
@@ -608,7 +608,12 @@
for (var i = 0 ; i < parsedBacktrace.length ; i++) {
html += '<div>';
html += '<div>' + (parsedBacktrace[i].fnName || '(anonymous function)') + '</div>';
html += '<div class="trace">' + getUrlLink(parsedBacktrace[i].filePath, 40) + ':' + parsedBacktrace[i].line + '</div>';
html += '<div class="trace">' + getUrlLink(parsedBacktrace[i].filePath, 40) + '</div>';
if (parsedBacktrace[i].column) {
html += '<div>' + parsedBacktrace[i].line + ':' + parsedBacktrace[i].column + '</div>';
} else {
html += '<div>line ' + parsedBacktrace[i].line + '</div>';
}
html += '</div>';
}
}
@@ -630,6 +635,17 @@
var fnName = null, fileAndLine;
var withFnResult = /^([^\s\(]+) \((.+:\d+)\)$/.exec(trace);
if (withFnResult === null) {
// Try the PhantomJS 2 format
withFnResult = /^([^\s\(]+) \((.+:\d+:\d+)\)$/.exec(trace);
}
if (withFnResult === null) {
// Try the PhantomJS 2 ERROR format
withFnResult = /^([^\s\(]+) (http.+:\d+)$/.exec(trace);
}
if (withFnResult === null) {
fileAndLine = trace;
} else {
@@ -637,14 +653,22 @@
fileAndLine = withFnResult[2];
}
var fileAndLineSplit = /^(.*):(\d+)$/.exec(fileAndLine);
// And now the second part
var fileAndLineSplit = /^(.*):(\d+):(\d+)$/.exec(fileAndLine);
if (fileAndLineSplit === null) {
fileAndLineSplit = /^(.*):(\d+)$/.exec(fileAndLine);
}
var filePath = fileAndLineSplit[1];
var line = fileAndLineSplit[2];
var column = fileAndLineSplit[3];
out.push({
fnName: fnName,
filePath: filePath,
line: line
line: line,
column: column
});
});
+1 -1
View File
@@ -14,7 +14,7 @@ apiService.factory('API', ['$location', 'Runs', 'Results', function($location, R
waitForSelector: settings.waitForSelector,
cookie: settings.cookie,
authUser: settings.authUser,
authPass: settings.authPass,
authPass: settings.authPass
};
if (settings.waitForSelector && settings.waitForSelector !== '') {
+5 -1
View File
@@ -108,7 +108,11 @@
.summary .notations .criteria .table {
width: 100%;
> div:hover > div {
> a {
text-decoration: none;
color: inherit;
}
> a:hover > div {
background: #EBD8E2;
cursor: pointer;
&.info {
+2 -1
View File
@@ -38,7 +38,7 @@
font-size: 1em;
}
input[type=text] {
input[type=text], input[type=password] {
width: 100%;
min-width: 4em;
}
@@ -147,6 +147,7 @@
&.label {
width: 25%;
white-space: nowrap;
vertical-align: middle;
}
}
}
+8 -9
View File
@@ -132,8 +132,12 @@ a.linkButton {
background: #000;
> div {
overflow: scroll;
position: relative;
overflow: hidden;
&:hover {
overflow: auto;
}
}
.screenshotImage {
@@ -234,14 +238,6 @@ a.linkButton {
}
}
.star {
font-weight: bold;
span {
font-size: 1.2em;
}
}
.footer {
padding: 3em;
color: #413;
@@ -251,6 +247,9 @@ a.linkButton {
.version {
font-size: 0.7em;
}
.github {
margin: 1em 0 0 0.5em;
}
}
[class^="icon-"]:before, [class*=" icon-"]:before {
+27 -4
View File
@@ -32,9 +32,18 @@
margin: 0 auto 0.5em;
}
.rule h3 {
margin-bottom: 0em;
}
.rule .okThreshold {
font-style: italic;
font-size: 0.9em;
}
.rule .message {
width: 80%;
margin: 0 auto;
margin: 1.5em auto;
p {
margin: 0.5em;
}
@@ -225,6 +234,19 @@
}
}
.similarColors {
margin: 1em;
width: 20em;
height: 6em;
> div {
display: inline-block;
width: 10em;
height: 3.5em;
padding-top: 2.5em;
}
}
.totalWeightPie {
max-width: 39em;
margin: 2em auto 4em;
@@ -262,8 +284,9 @@
}
.smallPreview {
max-height: 1.6em;
max-width: 4em;
display: block;
max-height: 4em;
max-width: 8em;
border: 1px solid #000;
margin-top: 0.2em;
margin: 1em auto 0.2em;
}
+4 -2
View File
@@ -173,7 +173,8 @@
border-spacing: 0.25em;
}
.table > div {
.table > div,
.table > a {
display: table-row;
}
@@ -182,7 +183,8 @@
padding: 0.5em 1em;
}
.table > div > div {
.table > div > div,
.table > a > div {
padding: 0.1em 1em;
background: #f2f2f2;
display: table-cell;
+11 -10
View File
@@ -16,18 +16,18 @@
<link rel="stylesheet" type="text/css" href="/css/screenshot.css">
<link rel="stylesheet" type="text/css" href="/css/timeline.css">
<link rel="stylesheet" type="text/css" href="/css/about.css">
<link rel="stylesheet" type="text/css" href="/bower_components/angular-chart.js/dist/angular-chart.css">
<link rel="stylesheet" type="text/css" href="/node_modules/angular-chart.js/dist/angular-chart.css">
<!-- endbuild -->
<!-- build:js /js/all.js -->
<script src="/bower_components/angular/angular.min.js"></script>
<script src="/bower_components/Chart.js/Chart.min.js"></script>
<script src="/bower_components/angular-route/angular-route.min.js"></script>
<script src="/bower_components/angular-resource/angular-resource.min.js"></script>
<script src="/bower_components/angular-sanitize/angular-sanitize.min.js"></script>
<script src="/bower_components/angular-animate/angular-animate.min.js"></script>
<script src="/bower_components/angular-local-storage/dist/angular-local-storage.min.js"></script>
<script src="/bower_components/angular-chart.js/dist/angular-chart.min.js"></script>
<script src="/node_modules/angular/angular.min.js"></script>
<script src="/node_modules/chart.js/Chart.min.js"></script>
<script src="/node_modules/angular-route/angular-route.min.js"></script>
<script src="/node_modules/angular-resource/angular-resource.min.js"></script>
<script src="/node_modules/angular-sanitize/angular-sanitize.min.js"></script>
<script src="/node_modules/angular-animate/angular-animate.min.js"></script>
<script src="/node_modules/angular-local-storage/dist/angular-local-storage.min.js"></script>
<script src="/node_modules/angular-chart.js/dist/angular-chart.min.js"></script>
<script src="/js/app.js"></script>
<script src="/js/controllers/indexCtrl.js"></script>
<script src="/js/controllers/dashboardCtrl.js"></script>
@@ -50,8 +50,9 @@
<div id="header"><h1>Yellow Lab <span class="icon-lab"></span> Tools</h1></div>
<div id="body" ng-view autoscroll="true"></div>
<div class="footer">
<a href="/about">Learn more</a><br>If you like <b>Yellow Lab Tools</b>, <a href="https://github.com/gmetais/YellowLabTools" target="_blank" class="star">give it a <span>&#9733;</span> on GitHub</a>!<br>
<span class="version">@@version</span>
<br><a href="/about">More about Yellow Lab Tools</a><br>
<div class="github"><iframe id="ghbtn" frameborder="0" scrolling="0" width="145px" height="30px"></iframe></div>
</div>
<script>
+4 -4
View File
@@ -28,8 +28,8 @@
<div class="category">{{category.label}}</div>
<div class="criteria">
<div class="table" title="Click to see details">
<div ng-repeat="ruleName in category.rules" ng-if="result.rules[ruleName]" ng-init="rule = result.rules[ruleName]"
ng-class="{'warning': rule.abnormal}" ng-click="showRulePage(ruleName)">
<a ng-repeat="ruleName in category.rules" ng-if="result.rules[ruleName]" ng-init="rule = result.rules[ruleName]"
ng-class="{'warning': rule.abnormal}" href="/result/{{runId}}/rule/{{ruleName}}">
<div class="grade">
<grade score="rule.score"></grade>
</div>
@@ -42,14 +42,14 @@
<span ng-if="rule.abnormalityScore <= -300" class="icon-warning"></span>
</div>
<div class="info"><span class="icon-question"></span></div>
</div>
</a>
</div>
</div>
</div>
</div>
<div ng-if="!error && !fromSocialShare" class="apiTip">
<b>Did you know? Yellow Lab Tools has a brand new <a href="https://github.com/gmetais/grunt-yellowlabtools" target="_blank">Grunt plugin</a>!
<b>Did you know?</b> You can <b>bookmark</b> this result page URL or <b>share it</b>, Yellow Lab Tools keeps test results for years!
</div>
<div class="tweet" ng-if="!error && !fromSocialShare">
+4 -4
View File
@@ -2,7 +2,7 @@
<p class="price">Free and open source!</p>
<form ng-submit="launchTest()" >
<input type="{{$root.isTouchDevice? 'url' : 'text'}}" name="url" ng-model="url" placeholder="http://www.mysite.com" class="url" />
<input type="text" name="url" ng-model="url" placeholder="http://www.mysite.com" class="url" />
<input type="submit" value="Launch test" class="launchBtn" ng-class="{disabled: !url}" />
<div class="settings">
<div class="device">
@@ -42,7 +42,7 @@
</div>
<div><input type="text" name="cookie" ng-model="settings.cookie" /></div>
</div>
<!--<div>
<div>
<div class="label">
Authent
<span class="settingsTooltip">
@@ -57,10 +57,10 @@
</div>
<div>
<div><span>password</div>
<div><input type="text" class="authField" name="authPass" ng-model="settings.authPass" /></div>
<div><input type="password" class="authField" name="authPass" ng-model="settings.authPass" /></div>
</div>
</div>
</div>-->
</div>
<!--<div>
<div class="label">Blocked domains</div>
<div>
+14 -2
View File
@@ -14,6 +14,14 @@
<span ng-if="rule.policy.unit == 'bytes'">{{rule.value | bytes}}</span>
<span ng-if="rule.policy.unit != 'bytes'">{{rule.value}}</span>
</h3>
<div class="okThreshold" ng-if="rule.score < 100 && rule.policy.isOkThreshold !== undefined">
Have
<span ng-if="rule.policy.unit == 'bytes'">{{rule.policy.isOkThreshold | bytes}}</span>
<span ng-if="rule.policy.unit != 'bytes'">{{rule.policy.isOkThreshold}}</span>
<span ng-if="rule.policy.isOkThreshold > 0 && rule.policy.isOkThreshold < rule.policy.isBadThreshold">or less</span>
<span ng-if="rule.policy.isOkThreshold > rule.policy.isBadThreshold">or more</span>
to get the 100/100 score.
</div>
<div ng-bind-html="rule.policy.message" class="message"></div>
</div>
</div>
@@ -88,6 +96,10 @@
</span>
</div>
<div ng-if="policyName === 'similarColors'">
<div class="similarColors checker"><div ng-style="{'background-color': offender.color1, 'color': offender.isDark ? '#FFF' : '#000'}">{{offender.color1}}</div><div ng-style="{'background-color': offender.color2, 'color': offender.isDark ? '#FFF' : '#000'}">{{offender.color2}}</div></div>
</div>
<div ng-if="policyName === 'cssParsingErrors'">
<b>{{offender.error}}</b>
<file-and-line file="offender.file" line="offender.line" column="offender.column"></file-and-line>
@@ -137,9 +149,9 @@
<file-and-line-button file="offender.file" line="offender.line" column="offender.column"></file-and-line-button>
</div>
<div ng-if="policyName === 'lazyLoadableImagesBelowTheFold'">
<url-link url="offender" max-length="100"></url-link>
<div ng-if="policyName === 'lazyLoadableImagesBelowTheFold' || policyName === 'hiddenImages'">
<img ng-src="{{offender}}" class="smallPreview checker"></img>
<url-link url="offender" max-length="100"></url-link>
</div>
<div ng-if="policyName === 'notFound' || policyName === 'closedConnections' || policyName === 'multipleRequests' || policyName === 'cachingDisabled' || policyName === 'cachingNotSpecified'">
+35 -11
View File
@@ -150,10 +150,10 @@ var policies = {
"DOMaccessesOnScroll": {
"tool": "jsExecutionTransformer",
"label": "DOM access on scroll",
"message": "<p>This rule counts the number of DOM-accessing functions calls on a scroll event, such as queries, readings, writings, bindings and jQuery functions.</p><p>Two scroll events are triggered quickly, one after the other, and only the second one is analyzed so throttled functions are ignored.</p><p>One of the main reasons of a poor scrolling experience is when too much JS is executed on each scroll event. Note that some devices such as smartphones and MacBooks send more scroll events than others.</p><p>Reduce the number of DOM accesses inside scroll listeners. Put DOM queries outside them when possible. Use <a href=\"http://blogorama.nerdworks.in/javascriptfunctionthrottlingan/\" target=\"_blank\">throttling or deboucing</a>.</p>",
"message": "<p>This rule counts the number of DOM-accessing functions calls on a scroll event, such as queries, readings, writings, bindings and jQuery functions.</p><p>Two scroll events are triggered quickly, one after the other, and only the second one is analyzed so throttled functions are ignored.</p><p>One of the main reasons of a poor scrolling experience is when too much JS is executed on each scroll event. Note that some devices such as smartphones and MacBooks send more scroll events than others.</p><p>Reduce the number of DOM accesses inside scroll listeners. Put DOM queries outside them when possible. Use <a href=\"http://blogorama.nerdworks.in/javascriptfunctionthrottlingan/\" target=\"_blank\">throttling or debouncing</a>.</p>",
"isOkThreshold": 1,
"isBadThreshold": 12,
"isAbnormalThreshold": 25,
"isBadThreshold": 20,
"isAbnormalThreshold": 35,
"hasOffenders": true,
"offendersTransformFn": function(offenders) {
return offenders;
@@ -195,8 +195,8 @@ var policies = {
"label": "document.write calls",
"message": "<p>They slow down the page construction, especially if they are used to insert scripts in the page. Remove them ASAP.</p><p>If you cannot remove them because they come from a third-party script (such as ads), have a look at <a href=\"https://github.com/krux/postscribe\" target=\"_blank\">PostScribe</a>.</p>",
"isOkThreshold": 0,
"isBadThreshold": 10,
"isAbnormalThreshold": 20,
"isBadThreshold": 5,
"isAbnormalThreshold": 10,
"hasOffenders": true,
"offendersTransformFn": function(offenders) {
return {
@@ -254,8 +254,8 @@ var policies = {
"tool": "phantomas",
"label": "Global variables",
"message": "<p>It is a bad practice because they clutter up the global namespace. If two scripts use the same variable name in the global scope, it can cause conflicts and it is generally hard to debug.</p><p>Global variables also take a (very) little bit longer to be accessed than variables in the local scope of a function.</p>",
"isOkThreshold": 30,
"isBadThreshold": 150,
"isOkThreshold": 40,
"isBadThreshold": 200,
"isAbnormalThreshold": 700,
"hasOffenders": true,
"offendersTransformFn": function(offenders) {
@@ -463,7 +463,7 @@ var policies = {
},
"cssColors": {
"tool": "phantomas",
"label": "Different colors",
"label": "Colors count",
"message": "<p>This is the number of different colors defined in CSS.</p><p>Your CSS project will be easier to maintain if you keep a small color set.</p>",
"isOkThreshold": 30,
"isBadThreshold": 150,
@@ -507,6 +507,21 @@ var policies = {
};
}
},
"similarColors": {
"tool": "colorDiff",
"label": "Similar colors",
"message": "<p>This is the list of colors found in the stylesheets, that are very close to each other. The eye can barely see the difference.</p><p>Use this list to reduce the number of colors in your palette, it will be easier to maintain.</p>",
"isOkThreshold": 0,
"isBadThreshold": 40,
"isAbnormalThreshold": 80,
"hasOffenders": true,
"offendersTransformFn": function(offenders) {
return {
count: offenders.length,
list: offenders
};
}
},
"cssImports": {
"tool": "phantomas",
"label": "Uses of @import",
@@ -846,7 +861,7 @@ var policies = {
"totalWeight": {
"tool": "weightChecker",
"label": "Total weight",
"message": "<p>The weight is of course very important if you want the page to load fast. Try to stay under 1MB, which is alreay very long to download over a slow connection.</p>",
"message": "<p>The weight is of course very important if you want the page to load fast. Try to stay under 1MB, which is alreay very long to download over a slow connection.</p><p>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.</p>",
"isOkThreshold": 716800,
"isBadThreshold": 2097152,
"isAbnormalThreshold": 3145728,
@@ -856,7 +871,7 @@ var policies = {
"imageOptimization": {
"tool": "weightChecker",
"label": "Image optimization",
"message": "<p>This metric mesures the number of bytes that could be saved by optimizing images.</p><p>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 <a href=\"https://kraken.io/\" target=\"_blank\">Kraken.io</a> or the excellent <a href=\"https://imageoptim.com/\" target=\"_blank\">ImageOption</a> on Mac. For SVG images, you can use <a href=\"https://jakearchibald.github.io/svgomg/\" target=\"_blank\">SVGOMG</a></p><p>The tools in use in YellowLabTools are not set to their maximum optimization power, so you might be able to compress even more!</p>",
"message": "<p>This metric mesures the number of bytes that could be saved by optimizing images.</p><p>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 <a href=\"https://kraken.io/\" target=\"_blank\">Kraken.io</a> or the excellent <a href=\"https://imageoptim.com/\" target=\"_blank\">ImageOptim</a> on Mac. For SVG images, you can use <a href=\"https://jakearchibald.github.io/svgomg/\" target=\"_blank\">SVGOMG</a></p><p>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!</p><p>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.</p>",
"isOkThreshold": 10240,
"isBadThreshold": 122880,
"isAbnormalThreshold": 307200,
@@ -959,13 +974,22 @@ var policies = {
},
"lazyLoadableImagesBelowTheFold": {
"tool": "phantomas",
"label": "Not lazyloaded images",
"label": "Below the fold images",
"message": "<p>This is the number of images displayed below the fold that could be lazy-loaded. This is an excellent way to accelerate the loading time of an heavy page.</p><p>I recommend using <a href=\"https://github.com/vvo/lazyload\" target=\"_blank\">this lazyloader</a>.</p>",
"isOkThreshold": 1,
"isBadThreshold": 12,
"isAbnormalThreshold": 30,
"hasOffenders": true
},
"hiddenImages": {
"tool": "phantomas",
"label": "Hidden images",
"message": "<p>List of all images that have a display:none property, or one of their parents. These images are loaded by the browser even if they're not visible. You might be able to find a way to lazy-load them, only when they get visible.</p><p>Trackers are an exception, you'd better hide them.</p>",
"isOkThreshold": 1,
"isBadThreshold": 12,
"isAbnormalThreshold": 30,
"hasOffenders": true
},
"cachingDisabled": {
"tool": "phantomas",
"label": "Caching disabled",
+4 -2
View File
@@ -17,7 +17,8 @@
"notFound": 3,
"multipleRequests": 2,
"smallRequests": 1,
"lazyLoadableImagesBelowTheFold": 2
"lazyLoadableImagesBelowTheFold": 2,
"hiddenImages": 1
}
},
"domComplexity": {
@@ -69,7 +70,8 @@
"cssRules": 2,
"cssComplexSelectors": 2,
"cssComplexSelectorsByAttribute": 1.5,
"cssColors": 0.5
"cssColors": 0.5,
"similarColors": 0.5
}
},
"badCSS": {
+3
View File
@@ -151,6 +151,9 @@ var OffendersHelpers = function() {
};
this.cssOffenderPattern = function(offender) {
// Remove any line breaks
offender = offender.replace(/(\r\n|\n|\r)/gm, '');
var parts = /^(.*) (?:<([^ \(]*)>|\[inline CSS\]) @ (\d+):(\d+)$/.exec(offender);
if (!parts) {
+4 -6
View File
@@ -3,6 +3,7 @@ var debug = require('debug')('ylt:runner');
var phantomasWrapper = require('./tools/phantomas/phantomasWrapper');
var jsExecutionTransformer = require('./tools/jsExecutionTransformer');
var colorDiff = require('./tools/colorDiff');
var weightChecker = require('./tools/weightChecker/weightChecker');
var rulesChecker = require('./rulesChecker');
var scoreCalculator = require('./scoreCalculator');
@@ -28,6 +29,9 @@ var Runner = function(params) {
// Treat the JS Execution Tree from offenders
data = jsExecutionTransformer.transform(data);
// Compare colors
data = colorDiff.compareAllColors(data);
// Redownload every file
return weightChecker.recheckAllFiles(data);
@@ -52,12 +56,6 @@ var Runner = function(params) {
delete data.toolsResults.phantomas.metrics.scrollExecutionTree;
delete data.toolsResults.phantomas.offenders.scrollExecutionTree;
return data;
})
.then(function(data) {
// Finished!
deferred.resolve(data);
+3 -2
View File
@@ -6,6 +6,7 @@ var ScreenshotHandler = require('../../screenshotHandler');
var RunsQueue = require('../datastores/runsQueue');
var RunsDatastore = require('../datastores/runsDatastore');
var ResultsDatastore = require('../datastores/resultsDatastore');
var serverSettings = require('../../../server_config/settings.json');
var ApiController = function(app) {
@@ -52,7 +53,6 @@ var ApiController = function(app) {
debug('Adding test %s to the queue', run.runId);
var queuePromise = queue.push(run.runId);
// Save the run to the datastore
runsDatastore.add(run, queuePromise.startingPosition);
@@ -76,7 +76,8 @@ var ApiController = function(app) {
waitForSelector: run.params.waitForSelector,
cookie: run.params.cookie,
authUser: run.params.authUser,
authPass: run.params.authPass
authPass: run.params.authPass,
phantomasEngine: serverSettings.phantomasEngine
};
return ylt(run.params.url, runOptions);
+1 -1
View File
@@ -20,7 +20,7 @@ var FrontController = function(app) {
app.use('/img', express.static(path.join(__dirname, assetsPath, 'img'), { maxAge: cacheDuration }));
app.use('/js', express.static(path.join(__dirname, assetsPath, 'js'), { maxAge: cacheDuration }));
app.use('/views', express.static(path.join(__dirname, assetsPath, 'views'), { maxAge: cacheDuration }));
app.use('/bower_components', express.static(path.join(__dirname, '../../../bower_components'), { maxAge: cacheDuration }));
app.use('/node_modules', express.static(path.join(__dirname, '../../../node_modules'), { maxAge: cacheDuration }));
};
module.exports = FrontController;
+90
View File
@@ -0,0 +1,90 @@
var debug = require('debug')('ylt:colorDiff');
var diff = require('color-diff');
var parse = require('parse-color');
var colorDiff = function() {
var COLOR_DIFF_THRESHOLD = 0.9;
this.compareAllColors = function(data) {
debug('Starting to compare all colors...');
var offenders = data.toolsResults.phantomas.offenders.cssColors;
var colors = (offenders) ? this.parseAllColors(offenders) : [];
var result = [];
// Compare colors to each other
for (var i = 0; i < colors.length - 1 ; i++) {
for (var j = i + 1; j < colors.length; j++) {
debug('Comparing color %s with color %s', colors[i].original, colors[j].original);
if (this.compareTwoColors(colors[i], colors[j])) {
debug('Colors are similar!');
result.push({
color1: colors[i].original,
color2: colors[j].original,
isDark: (colors[i].Lab.L < 60)
});
}
}
}
data.toolsResults.colorDiff = {
metrics: {
similarColors: result.length
},
offenders: {
similarColors: result
}
};
return data;
};
this.parseAllColors = function(offenders) {
var parsedOffenders = offenders.map(this.parseOffender);
var deduplicatedColors = {};
parsedOffenders.forEach(function(color) {
deduplicatedColors[color] = color;
});
return Object.keys(deduplicatedColors).map(this.parseColor);
};
this.parseOffender = function(offender) {
var regexResult = /^(.*) \(\d+ times\)$/.exec(offender);
return regexResult[1];
};
this.parseColor = function(color) {
var colorArray = parse(color).rgba;
var obj = {
R: colorArray[0],
G: colorArray[1],
B: colorArray[2],
A: colorArray[3]
};
obj.Lab = diff.rgb_to_lab(obj);
obj.original = color;
return obj;
};
this.compareTwoColors = function(color1, color2) {
if (color1.A !== color2.A) {
debug('Comparison not possible, because the alpha channel is different');
return false;
}
var delta = diff.diff(color1.Lab, color2.Lab);
debug('Delta is %d', delta);
return delta <= COLOR_DIFF_THRESHOLD;
};
};
module.exports = new colorDiff();
@@ -3,21 +3,23 @@
*/
/* global document: true, Node: true, window: true */
exports.version = '0.1.a';
exports.version = '1.0.a';
exports.module = function(phantomas) {
'use strict';
// total length of HTML of hidden elements (i.e. display: none)
phantomas.setMetric('hiddenContentSize'); // @desc the size of content of hidden elements on the page (with CSS display: none) @offenders
phantomas.setMetric('hiddenImages'); // @desc number of hidden images that can be lazy-loaded @offenders
// HTML size
phantomas.on('report', function() {
phantomas.evaluate(function() {
(function(phantomas) {
phantomas.spyEnabled(false, 'checking the hiddenContentSize');
var runner = new phantomas.nodeRunner(),
lazyLoadableImages = {};
var runner = new phantomas.nodeRunner();
phantomas.spyEnabled(false, 'analyzing hidden content');
runner.walk(document.body, function(node, depth) {
switch (node.nodeType) {
@@ -36,6 +38,29 @@ exports.module = function(phantomas) {
}
}
// count hidden images that can be lazy loaded (issue #524)
var images = [];
if (node.tagName === 'IMG') {
images = [node];
} else if (typeof node.querySelectorAll === 'function') {
images = node.querySelectorAll('img') || [];
}
for (var i = 0, len = images.length; i < len; i++) {
var src = images[i].src,
path;
if (src === '' || src.indexOf('data:image') === 0) continue;
if (!lazyLoadableImages[src]) {
path = phantomas.getDOMPath(images[i]);
lazyLoadableImages[src] = {
path: path
};
}
}
// don't run for child nodes as they're hidden as well
return false;
}
@@ -43,6 +68,15 @@ exports.module = function(phantomas) {
}
});
Object.keys(lazyLoadableImages).forEach(function(img) {
var entry = lazyLoadableImages[img];
phantomas.incrMetric('hiddenImages');
phantomas.addOffender('hiddenImages', img);
phantomas.log('hiddenImages: <%s> image (%s) is hidden and can be lazy-loaded', img, entry.path);
});
phantomas.spyEnabled(true);
}(window.__phantomas));
});
+2 -2
View File
@@ -17,11 +17,11 @@ var PhantomasWrapper = function() {
var deferred = Q.defer();
var task = data.params;
var options = {
// Cusomizable options
'timeout': task.options.timeout || 45,
'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) Chrome/27.0.1453.110 Safari/537.36' : null,
'tablet': (task.options.device === 'tablet'),
+1 -1
View File
@@ -121,7 +121,7 @@ var FileMinifier = function() {
});
} else {
debug('Not minfiable type or already minified');
debug('Not minifiable type or already minified');
deferred.resolve(entry);
}
+3 -1
View File
@@ -6,6 +6,7 @@ var zlib = require('zlib');
var GzipCompressor = function() {
function compressFile(entry) {
debug('Entering compressFile');
return gzipUncompressedFile(entry)
.then(gzipOptimizedFile);
@@ -41,7 +42,7 @@ var GzipCompressor = function() {
}
});
} else {
debug('Compression not needed');
deferred.resolve(entry);
}
@@ -73,6 +74,7 @@ var GzipCompressor = function() {
}
});
} else {
debug('Compressing optimized file not needed');
deferred.resolve(entry);
}
+1 -1
View File
@@ -200,7 +200,7 @@ var ImageOptimizer = function() {
} else if (type === 'png' && !lossy) {
engine = Imagemin.optipng({optimizationLevel: OPTIPNG_COMPRESSION_LEVEL});
} else if (type === 'svg' && !lossy) {
engine = Imagemin.svgo();
engine = Imagemin.svgo({ plugins: [ { removeUselessDefs: false } ] });
} else {
deferred.reject('No optimization engine found for imagemin');
}
+29 -2
View File
@@ -33,11 +33,19 @@ var WeightChecker = function() {
delete data.toolsResults.phantomas.metrics.requestsList;
delete data.toolsResults.phantomas.offenders.requestsList;
var httpAuth = null;
if (data.params && data.params.options && data.params.options.authUser && data.params.options.authPass) {
httpAuth = {
username: data.params.options.authUser,
password: data.params.options.authPass
};
}
// Transform every request into a download function with a callback when done
var redownloadList = requestsList.map(function(entry) {
return function(callback) {
redownloadEntry(entry)
redownloadEntry(entry, httpAuth)
.then(imageOptimizer.optimizeImage)
@@ -46,6 +54,7 @@ var WeightChecker = function() {
.then(gzipCompressor.compressFile)
.then(function(newEntry) {
debug('File %s - Redownloaded, optimized, minified, compressed: done', entry.url);
callback(null, newEntry);
})
@@ -155,6 +164,7 @@ var WeightChecker = function() {
requests.forEach(function(req) {
var weight = ((typeof req.weightCheck.bodySize === 'number') ? req.weightCheck.bodySize + req.weightCheck.headersSize : req.contentLength) || 0;
var type = req.type || 'other';
type = (results.byType[type]) ? type : 'other';
results.totalWeight += weight;
results.byType[type].totalWeight += weight;
@@ -275,6 +285,8 @@ var WeightChecker = function() {
requests.forEach(function(req) {
if (req.url !== 'about:blank') {
var type = req.type || 'other';
type = (results.byType[type]) ? type : 'other';
results.byType[type].push(req.url);
results.total ++;
}
@@ -309,7 +321,7 @@ var WeightChecker = function() {
}
function redownloadEntry(entry) {
function redownloadEntry(entry, httpAuth) {
var deferred = Q.defer();
function downloadError(message) {
@@ -363,6 +375,15 @@ var WeightChecker = function() {
timeout: REQUEST_TIMEOUT
};
// Basic auth
if (httpAuth) {
requestOptions.auth = {
user: httpAuth.username,
pass: httpAuth.password,
sendImmediately: false // Tries a first time without auth, wait for a 401 error before resending
};
}
download(requestOptions, entry.contentType, function(error, result) {
if (error) {
if (error.code === 'ETIMEDOUT') {
@@ -436,7 +457,9 @@ var WeightChecker = function() {
isCompressed = true;
tally();
}).on('error', function(err) {
debug('Error while decoding %s', requestOptions.url);
debug(err);
callback(err);
});
res.on('data', function (data) {
@@ -456,7 +479,9 @@ var WeightChecker = function() {
isCompressed = true;
tally();
}).on('error', function(err) {
debug('Error while decoding %s', requestOptions.url);
debug(err);
callback(err);
});
res.on('data', function (data) {
@@ -486,6 +511,8 @@ var WeightChecker = function() {
})
.on('error', function(err) {
debug('Error while downloading %s', requestOptions.url);
debug(err);
callback(err);
});
}
+61 -36
View File
@@ -1,7 +1,13 @@
{
"name": "yellowlabtools",
"version": "1.7.0",
"version": "1.8.0",
"description": "Online tool to audit a webpage for performance and front-end quality issues",
"license": "GPL-2.0",
"author": {
"name": "Gaël Métais",
"email": "gael@gaelmetais.com",
"url": "http://www.gaelmetais.com"
},
"repository": {
"type": "git",
"url": "git://github.com/gmetais/YellowLabTools.git"
@@ -11,53 +17,72 @@
},
"main": "./lib/index.js",
"dependencies": {
"async": "1.2.1",
"body-parser": "1.13.1",
"clean-css": "3.3.0",
"compression": "1.5.0",
"angular": "1.4.5",
"angular-animate": "1.4.5",
"angular-chart.js": "0.8.4",
"angular-local-storage": "0.2.2",
"angular-resource": "1.4.5",
"angular-route": "1.4.5",
"angular-sanitize": "1.4.5",
"async": "1.4.2",
"body-parser": "1.14.0",
"chart.js": "1.0.2",
"clean-css": "3.4.3",
"color-diff": "0.1.7",
"compression": "1.5.2",
"cors": "2.7.1",
"debug": "2.2.0",
"express": "4.12.4",
"express": "4.13.3",
"imagemin": "3.2.0",
"imagemin-jpegoptim": "4.0.0",
"jstoxml": "0.2.3",
"lwip": "0.0.7",
"meow": "3.3.0",
"minimize": "1.4.1",
"phantomas": "1.11.0",
"minimize": "1.7.1",
"parse-color": "1.0.0",
"phantomas": "1.12.0",
"ps-node": "0.0.4",
"q": "1.4.1",
"rimraf": "2.4.0",
"request": "2.62.0",
"rimraf": "2.4.3",
"temporary": "0.0.8",
"uglify-js": "2.4.23"
"uglify-js": "2.4.24"
},
"devDependencies": {
"chai": "^3.0.0",
"grunt": "^0.4.5",
"grunt-blanket": "^0.0.8",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-concat": "^0.5.1",
"grunt-contrib-copy": "^0.8.0",
"grunt-contrib-cssmin": "^0.12.3",
"grunt-contrib-htmlmin": "^0.4.0",
"grunt-contrib-jshint": "^0.11.2",
"grunt-contrib-less": "^1.0.1",
"grunt-contrib-uglify": "^0.9.1",
"grunt-env": "^0.4.4",
"grunt-express": "^1.4.1",
"grunt-filerev": "^2.3.1",
"grunt-inline-angular-templates": "^0.1.5",
"grunt-line-remover": "^0.0.2",
"grunt-mocha-test": "^0.12.7",
"grunt-replace": "^0.9.2",
"grunt-usemin": "^3.0.0",
"grunt-webfont": "^0.5.3",
"matchdep": "^0.3.0",
"mocha": "^2.2.5",
"request": "^2.58.0",
"sinon": "^1.15.3",
"sinon-chai": "^2.8.0"
"chai": "~3.2.0",
"grunt": "~0.4.5",
"grunt-blanket": "~0.0.8",
"grunt-contrib-clean": "~0.6.0",
"grunt-contrib-concat": "~0.5.1",
"grunt-contrib-copy": "~0.8.1",
"grunt-contrib-cssmin": "~0.14.0",
"grunt-contrib-htmlmin": "~0.4.0",
"grunt-contrib-jshint": "~0.11.3",
"grunt-contrib-less": "~1.0.1",
"grunt-contrib-uglify": "~0.9.2",
"grunt-env": "~0.4.4",
"grunt-express": "~1.4.1",
"grunt-filerev": "~2.3.1",
"grunt-inline-angular-templates": "~0.1.5",
"grunt-line-remover": "~0.0.2",
"grunt-mocha-test": "~0.12.7",
"grunt-replace": "~0.11.0",
"grunt-usemin": "~3.1.1",
"grunt-webfont": "~0.5.4",
"matchdep": "~0.3.0",
"mocha": "~2.3.2",
"sinon": "~1.16.1",
"sinon-chai": "~2.8.0"
},
"scripts": {
"test": "grunt test"
}
},
"keywords": [
"performance",
"metrics",
"webperf",
"pagespeed",
"budget",
"phantomas"
]
}
+3 -4
View File
@@ -2,7 +2,7 @@
# APT-GET
sudo apt-get update
sudo apt-get install lsb-release libfontconfig1 libfreetype6 -y --force-yes
sudo apt-get install lsb-release libfontconfig1 libfreetype6 libjpeg-dev -y --force-yes
sudo apt-get install curl git python-software-properties build-essential make g++ -y --force-yes
# Installation of NodeJS
@@ -12,7 +12,7 @@ sudo apt-get install -y nodejs
source ~/.profile
# Installation of some packages globally
npm install bower forever grunt-cli -g
npm install forever grunt-cli -g
source ~/.profile
# Installation of YellowLabTools
@@ -21,8 +21,7 @@ sudo chown $USER /space
cd /space
git clone https://github.com/gmetais/YellowLabTools.git --branch master
cd YellowLabTools
npm install
bower install --config.interactive=false --allow-root
npm install || exit 1
# Front-end compilation
grunt build
+2 -4
View File
@@ -10,11 +10,9 @@ git stash
git pull
git stash pop
# In case something was added in package.json or bower.json
# In case something was added in package.json
rm -rf node_modules
npm install
rm -rf bower_components
bower install --config.interactive=false --allow-root
npm install || exit 1
# Front-end compilation
rm -rf front/build
+1
View File
@@ -1,5 +1,6 @@
{
"serverPort": 80,
"phantomasEngine": "webkit",
"googleAnalyticsId": "",
"authorizedKeys": {
+1
View File
@@ -1,5 +1,6 @@
{
"serverPort": 8383,
"phantomasEngine": "webkit",
"googleAnalyticsId": "",
"authorizedKeys": {
+92
View File
@@ -0,0 +1,92 @@
var should = require('chai').should();
var colorDiff = require('../../lib/tools/colorDiff');
describe('colorDiff', function() {
it('should parse offenders correctly', function() {
colorDiff.parseOffender('#000 (1 times)').should.equal('#000');
colorDiff.parseOffender('#5bc0de (2 times)').should.equal('#5bc0de');
colorDiff.parseOffender('rgba(0,0,0,0.075) (100 times)').should.equal('rgba(0,0,0,0.075)');
colorDiff.parseOffender('rgb(91,192,222) (1000 times)').should.equal('rgb(91,192,222)');
});
it('should parse colors correctly', function() {
colorDiff.parseColor('#000').should.deep.equal({R: 0, G: 0, B: 0, A: 1, Lab: {L: 0, a: 0, b: 0}, original: '#000'});
colorDiff.parseColor('rgba(255,255,255,0.5)').should.deep.equal({R: 255, G: 255, B: 255, A: 0.5, Lab: {L: 100, a: 0.00526049995830391, b: -0.010408184525267927}, original: 'rgba(255,255,255,0.5)'});
});
it('should not compare colors with different alpha', function() {
var color1 = colorDiff.parseColor("rgba(0, 0, 0, 1)");
var color2 = colorDiff.parseColor("rgba(0, 0, 0, 0.5)");
colorDiff.compareTwoColors(color1, color2).should.equal(false);
});
it('should find that two colors are similar', function() {
var color1 = colorDiff.parseColor("rgba(0, 0, 0, 1)");
var color2 = colorDiff.parseColor("rgba(0, 0, 1, 1)");
colorDiff.compareTwoColors(color1, color2).should.equal(true);
});
it('should find that two colors are similar even with alpha', function() {
var color1 = colorDiff.parseColor("rgba(0, 0, 0, 0.3)");
var color2 = colorDiff.parseColor("rgba(0, 0, 2, 0.3)");
colorDiff.compareTwoColors(color1, color2).should.equal(true);
});
it('should find that two colors are different', function() {
var color1 = colorDiff.parseColor("rgba(0, 0, 0, 1)");
var color2 = colorDiff.parseColor("rgba(99, 99, 99, 1)");
colorDiff.compareTwoColors(color1, color2).should.equal(false);
});
it('should compare all colors to each other', function() {
var colors = [
'#000 (1 times)',
'#5bc0de (2 times)',
'rgba(0,0,0,0.075) (100 times)',
'rgb(91,192,222) (1000 times)',
'rgba(0,0,2,1) (1 times)',
'rgba(99,99,99,1) (1 times)',
'rgba(100,100,100,1) (1 times)'
];
var data = {
toolsResults: {
phantomas: {
offenders: {
cssColors: colors
}
}
}
};
newData = colorDiff.compareAllColors(data);
newData.toolsResults.should.have.a.property('colorDiff');
newData.toolsResults.colorDiff.should.have.a.property('metrics');
newData.toolsResults.colorDiff.metrics.should.have.a.property('similarColors').that.equals(3);
newData.toolsResults.colorDiff.should.have.a.property('offenders');
newData.toolsResults.colorDiff.offenders.should.have.a.property('similarColors').that.deep.equals([
{
color1: '#000',
color2: 'rgba(0,0,2,1)',
isDark: true
},
{
color1: '#5bc0de',
color2: 'rgb(91,192,222)',
isDark: false
},
{
color1: 'rgba(99,99,99,1)',
color2: 'rgba(100,100,100,1)',
isDark: true
}
]);
});
});
+11
View File
@@ -299,6 +299,17 @@ describe('offendersHelpers', function() {
});
});
it('should handle line breaks inside the string', function() {
var result = offendersHelpers.cssOffenderPattern('.card-mask-wrap { -moz-transform: translate3d(0, \n-288px\n, 0) } // was required by firefox 15 and earlier [inline CSS] @ 29:3');
result.should.deep.equal({
css: '.card-mask-wrap { -moz-transform: translate3d(0, -288px, 0) } // was required by firefox 15 and earlier',
file: null,
line: 29,
column: 3
});
});
});
describe('fileWithSizePattern', function() {
+16 -4
View File
@@ -84,6 +84,18 @@ describe('weightChecker', function() {
isCSS: true,
type: 'css'
},
{
method: 'GET',
url: 'http://localhost:8388/xml.xml',
requestHeaders: {
'User-Agent': 'something',
Referer: 'http://www.google.fr/',
Accept: '*/*'
},
status: 200,
isXML: true,
type: 'xml'
},
{
method: 'GET',
url: 'about:blank',
@@ -122,7 +134,7 @@ describe('weightChecker', function() {
data.toolsResults.weightChecker.offenders.totalWeight.byType.js.requests.length.should.equal(2);
data.toolsResults.weightChecker.offenders.totalWeight.byType.css.requests.length.should.equal(1);
data.toolsResults.weightChecker.offenders.totalWeight.byType.image.requests.length.should.equal(2);
data.toolsResults.weightChecker.offenders.totalWeight.byType.other.requests.length.should.equal(0);
data.toolsResults.weightChecker.offenders.totalWeight.byType.other.requests.length.should.equal(1);
data.toolsResults.weightChecker.offenders.should.have.a.property('imageOptimization');
data.toolsResults.weightChecker.offenders.imageOptimization.totalGain.should.be.above(0);
@@ -130,13 +142,13 @@ describe('weightChecker', function() {
data.toolsResults.weightChecker.offenders.should.have.a.property('gzipCompression');
data.toolsResults.weightChecker.offenders.gzipCompression.totalGain.should.be.above(0);
data.toolsResults.weightChecker.offenders.gzipCompression.files.length.should.equal(4);
data.toolsResults.weightChecker.offenders.gzipCompression.files.length.should.equal(5);
data.toolsResults.weightChecker.offenders.should.have.a.property('fileMinification');
data.toolsResults.weightChecker.offenders.fileMinification.totalGain.should.be.above(0);
data.toolsResults.weightChecker.offenders.fileMinification.files.length.should.equal(2);
data.toolsResults.weightChecker.metrics.should.have.a.property('totalRequests').that.equals(6);
data.toolsResults.weightChecker.metrics.should.have.a.property('totalRequests').that.equals(7);
data.toolsResults.weightChecker.offenders.should.have.a.property('totalRequests');
data.toolsResults.weightChecker.offenders.totalRequests.byType.html.length.should.equal(1);
data.toolsResults.weightChecker.offenders.totalRequests.byType.js.length.should.equal(2);
@@ -145,7 +157,7 @@ describe('weightChecker', function() {
data.toolsResults.weightChecker.offenders.totalRequests.byType.json.length.should.equal(0);
data.toolsResults.weightChecker.offenders.totalRequests.byType.webfont.length.should.equal(0);
data.toolsResults.weightChecker.offenders.totalRequests.byType.video.length.should.equal(0);
data.toolsResults.weightChecker.offenders.totalRequests.byType.other.length.should.equal(0);
data.toolsResults.weightChecker.offenders.totalRequests.byType.other.length.should.equal(1);
data.toolsResults.weightChecker.metrics.should.have.a.property('smallRequests').that.equals(0);
data.toolsResults.weightChecker.offenders.should.have.a.property('smallRequests');
+1
View File
@@ -1,5 +1,6 @@
{
"serverPort": "8387",
"phantomasEngine": "webkit",
"googleAnalyticsId": "",
"authorizedKeys": {
+80
View File
@@ -0,0 +1,80 @@
<?xml version="1.0"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<book id="bk103">
<author>Corets, Eva</author>
<title>Maeve Ascendant</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-11-17</publish_date>
<description>After the collapse of a nanotechnology
society in England, the young survivors lay the
foundation for a new society.</description>
</book>
<book id="bk104">
<author>Corets, Eva</author>
<title>Oberon's Legacy</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-03-10</publish_date>
<description>In post-apocalypse England, the mysterious
agent known only as Oberon helps to create a new life
for the inhabitants of London. Sequel to Maeve
Ascendant.</description>
</book>
<book id="bk105">
<author>Corets, Eva</author>
<title>The Sundered Grail</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-09-10</publish_date>
<description>The two daughters of Maeve, half-sisters,
battle one another for control of England. Sequel to
Oberon's Legacy.</description>
</book>
<book id="bk106">
<author>Randall, Cynthia</author>
<title>Lover Birds</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-09-02</publish_date>
<description>When Carla meets Paul at an ornithology
conference, tempers fly as feathers get ruffled.</description>
</book>
<book id="bk107">
<author>Thurman, Paula</author>
<title>Splish Splash</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-11-02</publish_date>
<description>A deep sea diver finds true love twenty
thousand leagues beneath the sea.</description>
</book>
<book id="bk108">
<author>Knorr, Stefan</author>
<title>Creepy Crawlies</title>
<genre>Horror</genre>
<price>4.95</price>
<publish_date>2000-12-06</publish_date>
<description>An anthology of horror stories about roaches,
centipedes, scorpions and other insects.</description>
</book>
</catalog>