Compare commits

...
56 Commits
Author SHA1 Message Date
Gaël MétaisandGitHub 73c5b3383e Merge pull request #391 for YLT v3.0
YLT v3.0 is coming soon!
2023-10-12 00:30:16 +02:00
Gaël MétaisandGitHub 5f09fb3bde Merge branch 'develop' into webp-avif 2023-10-06 16:43:32 +02:00
Gaël Métais 0610fd6fbb Update Phantomas and increase min NodeJS version 2023-10-06 16:18:23 +02:00
Gaël Métais 85c685a5ca Tag a phantomas version 2023-08-16 09:37:34 +02:00
Gaël Métais 2977506cb8 Adjustments to excessive image density 2023-08-16 07:57:01 +02:00
Gaël Métais 538d734a5b Switch imageExcessiveDensity to KB instead of simple count 2023-08-16 01:07:17 +02:00
Gaël Métais e680f429eb Re-introduce the Requests Number metric 2023-08-16 01:05:13 +02:00
Gaël Métais c308850eb3 WIP new image analizis module in Phantomas 2023-08-14 01:54:17 +02:00
Gaël Métais 8e941233a0 Revert the image reformater (moved to Phantomas) 2023-08-12 04:29:43 +02:00
Gaël MétaisandGitHub 22a8a206ab Merge pull request #388 from YellowLabTools/revert-387-webp-avif
Revert "Introduce a new "Old Image Formats" rule"
2023-08-12 04:08:02 +02:00
Gaël MétaisandGitHub 773249ca4f Revert "Introduce a new "Old Image Formats" rule" 2023-08-12 04:07:03 +02:00
Gaël MétaisandGitHub 599d02ea0e Merge pull request #387 from YellowLabTools/webp-avif
Introduce a new "Old Image Formats" rule
2023-07-30 22:47:10 +02:00
Gaël Métais 675316c9b7 Wording 2023-07-26 18:02:21 +02:00
Gaël Métais c22fd8b132 Add more information to the Old Image Formats offenders 2023-07-26 17:28:53 +02:00
Gaël Métais c4b008f910 New rule that converts images to WebP and AVIF and checks the gain 2023-07-26 16:04:20 +02:00
Gaël Métais 0cde5a3607 Add Avif to content type detection 2023-07-21 10:17:18 +02:00
Gaël Métais 6180b9f86c Replace Jimp by Sharp for image processing (faster + supports webp/avif) 2023-07-20 17:38:14 +02:00
Gaël MétaisandGitHub daae7d8523 Merge pull request #386 from YellowLabTools/aws
Starting to prepare version 3.0.0
2023-07-20 16:33:05 +02:00
Gaël Métais 540e5b5ac0 Version 3.0.0 2023-07-20 16:10:46 +02:00
Gaël MétaisandGitHub 4b671001e0 Merge branch 'develop' into aws 2023-07-20 15:39:06 +02:00
Gaël Métais 1ae4bed339 Update more dependencies 2023-07-19 22:01:29 +02:00
Gaël Métais 04e734bd5a Updating some dependancies 2023-07-19 00:14:36 +02:00
Gaël Métais a0e71ef444 No support for IE10 when minifying css files 2023-07-19 00:13:44 +02:00
Gaël Métais 05d8da36d8 Fix tests 2023-07-19 00:12:51 +02:00
Gaël Métais 5cae61b896 Making ylt server work on both normal and AWS machines 2023-07-16 10:47:58 +02:00
Gaël Métais a319e84831 v2.2.0 2022-02-24 14:27:05 +01:00
Gaël MétaisandGitHub d38781400e Merge pull request #359 from EXXETA/add-storage-handling
Storage handling feature from phantomas 2.4.0
2022-02-24 14:21:32 +01:00
Kevin Kammleiter cbcc6647f2 feat(storage-handling): passthrough session and local storage 2022-01-17 13:13:07 +01:00
Gaël Métais d2a217319d Bring back the screenshot handler code 2021-05-11 00:12:25 +01:00
Gaël Métais 280b564ab3 Fix bug when the node module is called without any option 2021-05-11 00:11:47 +01:00
Gaël Métais 0e14b5bd04 Remove Lambda specific files, they will be commited into a new repository YellowLabTools/YellowLabTools-lambda-agent 2021-05-10 22:34:38 +01:00
Gaël Métais 90ded3e1cb Merge split into aws branch 2021-05-10 21:53:08 +01:00
Gaël Métais 163eef58c2 Business errors differenciated from technical errors in the Lambda runner 2021-04-11 13:14:35 +01:00
Gaël Métais 201bf75bf7 Fix unused unicode ranges metric 2021-04-04 23:43:55 +01:00
Gaël Métais ac37ebb4e2 Fix crash 2021-03-22 00:10:31 +00:00
Gaël Métais 16c3abf3f2 Remove JSHint from build steps 2021-03-22 00:06:18 +00:00
Gaël Métais d1681c3d88 Temporarily remove queue messages 2021-03-21 23:53:04 +00:00
Gaël Métais 97099e9f65 Add runId quickfix 2021-03-21 23:15:32 +00:00
Gaël Métais 4e8cb19f55 Add runId in the results 2021-03-21 23:13:56 +00:00
Gaël Métais 47eab96a67 Two version of the API: normal/lambda. Will be merged later 2021-03-20 18:24:11 +00:00
Gaël Métais ec7cba8f49 Disable browser cache in Docker 2021-03-20 18:21:28 +00:00
Gaël Métais e0d497796a Save screenshot when called with a saveFile option 2021-03-16 07:45:49 +00:00
Gaël Métais c5bc02d45d Move settings invocation to init phase 2021-03-16 07:45:10 +00:00
Gaël Métais e2b713282c Fix error Service should be called with new 2021-03-16 07:44:18 +00:00
Gaël Métais 05c883fa3e Temporarily use a fork of Phantomas for Characters Count 2021-03-15 23:22:22 +00:00
Gaël Métais e5d9680b23 Merge branch 'aws' of https://github.com/YellowLabTools/YellowLabTools into aws 2021-03-15 23:20:54 +00:00
Gaël MétaisandGitHub 4a40483348 Merge pull request #337 from ohoareau/master
feat: add Dockerfile, Makefile and lambda handler to create image for AWS Lambda
2021-03-15 23:20:35 +00:00
Gaël MétaisandGitHub 5f12d1ce0e Merge branch 'aws' into master 2021-03-15 23:19:26 +00:00
Gaël Métais 050b2303f0 Merge branch 'master' into aws 2021-03-15 23:17:37 +00:00
Olivier Hoareau 82294983b0 feat: add Dockerfile to create image for Lambda 2021-03-15 11:25:08 +01:00
Gaël Métais 8558ea29df Run YLT on AWS Lambda. TODO: should work both ways 2021-03-15 01:49:28 +00:00
Gaël Métais 9211074b32 It is now possible to save the results to an AWS S3 bucket 2021-03-14 01:49:23 +00:00
Gaël Métais 2da4371847 The screenshot is now saved in the system /tmp folder 2021-03-14 01:35:26 +00:00
Gaël Métais 791bf9fc8d Remove Yarn.lock 2021-03-13 23:58:56 +00:00
Gaël Métais e4b324d209 Clean cli.js 2021-03-13 23:57:06 +00:00
Gaël Métais 3b0bd172ba Clean results from redownload details 2021-03-13 16:08:31 +00:00
19 changed files with 376 additions and 272 deletions
+3 -1
View File
@@ -4,4 +4,6 @@ package-lock.json
tmp tmp
coverage coverage
package-lock.json package-lock.json
har.json yarn.lock
har.json
.idea/
+6 -2
View File
@@ -15,7 +15,6 @@ var cli = meow({
'Options:', 'Options:',
' --device Simulates a device. Choose between phone (default), tablet, desktop and desktop-hd.', ' --device Simulates a device. Choose between phone (default), tablet, desktop and desktop-hd.',
' --screenshot Will take a screenshot and use this value as the output path. It needs to end with ".png".', ' --screenshot Will take a screenshot and use this value as the output path. It needs to end with ".png".',
//' --wait-for-selector Once the page is loaded, Phantomas will wait until the given CSS selector matches some elements.',
' --proxy Sets an HTTP proxy to pass through. Syntax is "host:port".', ' --proxy Sets an HTTP proxy to pass through. Syntax is "host:port".',
' --cookie Adds a cookie on the main domain.', ' --cookie Adds a cookie on the main domain.',
' --auth-user Basic HTTP authentication username.', ' --auth-user Basic HTTP authentication username.',
@@ -24,6 +23,8 @@ var cli = meow({
' --allow-domain Only allow requests to given (comma-separated) domains.', ' --allow-domain Only allow requests to given (comma-separated) domains.',
' --no-externals Block all domains except the main one.', ' --no-externals Block all domains except the main one.',
' --reporter The output format: "json" or "xml". Default is "json".', ' --reporter The output format: "json" or "xml". Default is "json".',
' --local-storage Ability to set a local storage, key-value pairs (e.g. "bar=foo;domain=url")',
' --session-storage Ability to set a session storage, key-value pairs (e.g. "bar=foo;domain=url")',
'' ''
].join('\n'), ].join('\n'),
pkg: require('../package.json') pkg: require('../package.json')
@@ -75,6 +76,10 @@ options.blockDomain = cli.flags.blockDomain || null;
options.allowDomain = cli.flags.allowDomain || null; options.allowDomain = cli.flags.allowDomain || null;
options.noExternals = cli.flags.noExternals || null; options.noExternals = cli.flags.noExternals || null;
// Storage injection
options.localStorage = cli.flags.localStorage;
options.sessionStorage = cli.flags.sessionStorage;
// Output format // Output format
if (cli.flags.reporter && cli.flags.reporter !== 'json' && cli.flags.reporter !== 'xml') { if (cli.flags.reporter && cli.flags.reporter !== 'json' && cli.flags.reporter !== 'xml') {
console.error('Incorrect parameters: reporter has to be "json" or "xml"'); console.error('Incorrect parameters: reporter has to be "json" or "xml"');
@@ -98,7 +103,6 @@ if (cli.flags.reporter && cli.flags.reporter !== 'json' && cli.flags.reporter !=
// Remove some heavy parts of the results object // Remove some heavy parts of the results object
delete data.toolsResults; delete data.toolsResults;
delete data.javascriptExecutionTree;
var xmlOutput = serializer.render(data); var xmlOutput = serializer.render(data);
+44 -10
View File
@@ -1,13 +1,13 @@
var Q = require('q'); var debug = require('debug')('ylt:index');
var Q = require('q');
var Runner = require('./runner'); var Runner = require('./runner');
var ScreenshotHandler = require('./screenshotHandler');
var packageJson = require('../package.json'); var packageJson = require('../package.json');
var yellowLabTools = function(url, options) { var yellowLabTools = function(url, options) {
'use strict';
var deferred = Q.defer(); var deferred = Q.defer();
if (!url) { if (!url) {
@@ -27,15 +27,49 @@ var yellowLabTools = function(url, options) {
var runner = new Runner(params) var runner = new Runner(params)
.progress(deferred.notify) .progress(deferred.notify)
.then(function(data) { .then(function(data) {
// If a screenshot saveFunction was provided in the options
if (options && typeof options.saveScreenshotFn === 'function') {
debug('Now optimizing screenshot...');
// TODO: temporarily set all screenshot sizes to 600px, until we find a solution
ScreenshotHandler.findAndOptimizeScreenshot(data.params.options.screenshot, 600)
.then(function(screenshotBuffer) {
debug('Screenshot optimized, now saving...');
return options.saveScreenshotFn('screenshot.jpg', screenshotBuffer);
})
.then(function(response) {
debug('Screenshot saved');
debug(response);
// Remove uneeded temp screenshot path
delete data.params.options.screenshot;
})
.catch(function(err) {
// It's ok if we can't save the screenshot
debug('Screenshot could not be saved');
debug(err);
})
.finally(function() {
deferred.resolve(data);
});
} else {
deferred.resolve(data); deferred.resolve(data);
}) }
})
.fail(function(err) { .catch(function(err) {
deferred.reject(err); deferred.reject(err);
}); });
} }
return deferred.promise; return deferred.promise;
+89 -15
View File
@@ -54,7 +54,7 @@ var policies = {
"isBadThreshold": 2000, "isBadThreshold": 2000,
"isAbnormalThreshold": 4000, "isAbnormalThreshold": 4000,
"hasOffenders": false, "hasOffenders": false,
"unit": 'ms' "unit": "ms"
}, },
"DOMaccesses": { "DOMaccesses": {
"tool": "domAccessAgregator", "tool": "domAccessAgregator",
@@ -734,22 +734,96 @@ var policies = {
"isBadThreshold": 3145728, "isBadThreshold": 3145728,
"isAbnormalThreshold": 5242880, "isAbnormalThreshold": 5242880,
"hasOffenders": true, "hasOffenders": true,
"unit": 'bytes' "unit": "bytes"
}, },
"imageOptimization": { "imagesNotOptimized": {
"tool": "redownload", "tool": "phantomas",
"label": "Image optimization", "label": "Image optimization",
"message": "<p>This metric measures 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>", "message": "<p>This metric measures 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, so you might be able to compress even more!</p>",
"isOkThreshold": 20480, "isOkThreshold": 2048,
"isBadThreshold": 204800, "isBadThreshold": 204800,
"isAbnormalThreshold": 307200, "isAbnormalThreshold": 307200,
"hasOffenders": true, "hasOffenders": true,
"unit": 'bytes' "unit": "bytes",
"valueTransformFn": function(offenders) {
let totalGain = 0;
offenders.forEach((offender) => {
offender.gain = offender.fileSize - offender.newFileSize;
totalGain += offender.gain;
});
return totalGain;
},
"offendersTransformFn": function(offenders) {
return offenders;
}
}, },
"imagesTooLarge": { "imagesOldFormat": {
"tool": "redownload", "tool": "phantomas",
"label": "Old image formats",
"message": "<p>This metric goes further than \"Image optimization\". Measures the number of bytes that could be saved by converting images to newer and more efficient formats. The best image format is generally AVIF and the second best is WebP.</p><p>Be careful, you need to provide fallback images for old browsers and search engine bots.</p>",
"isOkThreshold": 2048,
"isBadThreshold": 512000,
"isAbnormalThreshold": 819200,
"hasOffenders": true,
"unit": "bytes",
"valueTransformFn": function(offenders) {
let totalGain = 0;
offenders.forEach((offender) => {
offender.gain = offender.fileSize - offender.newFileSize;
totalGain += offender.gain;
});
return totalGain;
},
"offendersTransformFn": function(offenders) {
return offenders;
}
},
"imagesScaledDown": {
"tool": "phantomas",
"label": "Oversized images", "label": "Oversized images",
"message": "<p>This is the number of images with a width >1200px on mobile, >1800px on tablet, >2400 on desktop, >3200px on HD desktop. Try reducing their size.</p><p>Please ignore if the file is used as a sprite.</p>", "message": "<p>This rule compares the number of pixels in a loaded images to the number of physical pixels it is displayed on. Then it estimates the number of KB that could be saved by serving it with the correct dimensions.</p><p>Of course, it is hard to serve perfect images for all screens. For this reason, this rule is quite permissive.</p>",
"isOkThreshold": 2048,
"isBadThreshold": 307200,
"isAbnormalThreshold": 512000,
"hasOffenders": true,
"unit": "bytes",
"valueTransformFn": function(offenders) {
let totalGain = 0;
offenders.forEach((offender) => {
offender.gain = offender.fileSize - offender.newFileSize;
totalGain += offender.gain;
});
return totalGain;
},
"offendersTransformFn": function(offenders) {
return offenders;
}
},
"imagesExcessiveDensity": {
"tool": "phantomas",
"label": "Excessive image density",
"message": "<p>This metric measures the number of bytes that could be saved by going further than just resizing images.<p>Devices with very high pixel density screen (such as 3x or 4x) are programmed to load high density images. This is the normal behavior, however the human eye barely sees the difference over 2x. This metric alerts you if an image density is > 2.2x.</p><p>There is currently no browser functionnality to prevent the issue (for this reason its impact on global score is low). But you can build your own clever solution!</p>",
"isOkThreshold": 102400,
"isBadThreshold": 307200,
"isAbnormalThreshold": 512000,
"hasOffenders": true,
"unit": "bytes",
"valueTransformFn": function(offenders) {
let totalGain = 0;
offenders.forEach((offender) => {
offender.gain = offender.fileSize - offender.newFileSize;
totalGain += offender.gain;
});
return totalGain;
},
"offendersTransformFn": function(offenders) {
return offenders;
}
},
"imagesWithIncorrectSizesParam": {
"tool": "phantomas",
"label": "Incorrect sizes parameter",
"message": "<p>When using an adaptative image with a <i>srcset</i> attribute and <i>w</i> values, it is important to correctly set the <i>sizes</i> attribute. Otherwise, the browser might pick the wrong image in the <i>srcset</i>.</p><p>The <a href=\”https://chrome.google.com/webstore/detail/responsive-image-linter/mnddginionlghpblkimpdalcecpnbjln\" target=\"_blank\">Responsive Image Linter</a> extension for Chrome can help you further.</p>",
"isOkThreshold": 0, "isOkThreshold": 0,
"isBadThreshold": 5, "isBadThreshold": 5,
"isAbnormalThreshold": 10, "isAbnormalThreshold": 10,
@@ -763,7 +837,7 @@ var policies = {
"isBadThreshold": 204800, "isBadThreshold": 204800,
"isAbnormalThreshold": 409600, "isAbnormalThreshold": 409600,
"hasOffenders": true, "hasOffenders": true,
"unit": 'bytes' "unit": "bytes"
}, },
"fileMinification": { "fileMinification": {
"tool": "redownload", "tool": "redownload",
@@ -773,12 +847,12 @@ var policies = {
"isBadThreshold": 61440, "isBadThreshold": 61440,
"isAbnormalThreshold": 122880, "isAbnormalThreshold": 122880,
"hasOffenders": true, "hasOffenders": true,
"unit": 'bytes' "unit": "bytes"
}, },
"totalRequests": { "totalRequests": {
"tool": "redownload", "tool": "redownload",
"label": "Requests number", "label": "Requests number",
"message": "<p>Each request slows down the page loading, especially on the protocol HTTP/1, but also a little on HTTP/2.</p><p>There are several technics to reduce their number:<ul><li>Concatenate JS files</li><li>Concatenate CSS files</li><li>Embed or inline small JS or CSS files in the HTML</li><li>Create sprites</li><li>Base64 encode small images in HTML or stylesheets</li><li>Use lazyloading for images</li></ul></p>", "message": "<p>Each request slows down the page loading, especially on the protocol HTTP/1, but also a little on HTTP/2 or 3.</p><p>There are several technics to reduce their number:<ul><li>Concatenate JS files</li><li>Concatenate CSS files</li><li>Embed or inline small JS or CSS files in the HTML</li><li>Create sprites</li><li>Base64 encode small images in HTML or stylesheets</li><li>Use lazyloading for images</li></ul></p>",
"isOkThreshold": 80, "isOkThreshold": 80,
"isBadThreshold": 240, "isBadThreshold": 240,
"isAbnormalThreshold": 320, "isAbnormalThreshold": 320,
@@ -869,7 +943,7 @@ var policies = {
"isOkThreshold": 0, "isOkThreshold": 0,
"isBadThreshold": 102400, "isBadThreshold": 102400,
"isAbnormalThreshold": 204800, "isAbnormalThreshold": 204800,
"unit": 'bytes', "unit": "bytes",
"hasOffenders": true, "hasOffenders": true,
"offendersTransformFn": function(offenders) { "offendersTransformFn": function(offenders) {
return offenders; return offenders;
@@ -895,7 +969,7 @@ var policies = {
"isBadThreshold": 51200, "isBadThreshold": 51200,
"isAbnormalThreshold": 122880, "isAbnormalThreshold": 122880,
"hasOffenders": true, "hasOffenders": true,
"unit": 'bytes' "unit": "bytes"
}, },
"oldHttpProtocol": { "oldHttpProtocol": {
"label": "HTTP protocols", "label": "HTTP protocols",
+26 -23
View File
@@ -1,23 +1,38 @@
{ {
"globalScore": {
"pageWeight": 2,
"images": 2,
"domComplexity": 1,
"javascriptComplexity": 2,
"badJavascript": 2,
"jQuery": 0.5,
"cssComplexity": 0.5,
"badCSS": 1,
"fonts": 1,
"serverConfig": 1
},
"categories": { "categories": {
"pageWeight": { "pageWeight": {
"label": "Page weight", "label": "Network",
"policies": { "policies": {
"totalWeight": 5, "totalWeight": 5,
"imageOptimization": 2,
"imagesTooLarge": 1,
"compression": 2,
"fileMinification": 2
}
},
"requests": {
"label": "Requests",
"policies": {
"totalRequests": 2, "totalRequests": 2,
"domains": 3, "domains": 3,
"notFound": 2, "compression": 2,
"fileMinification": 2,
"identicalFiles": 2, "identicalFiles": 2,
"emptyRequests": 3, "emptyRequests": 3,
"notFound": 2
}
},
"images": {
"label": "Images",
"policies": {
"imagesNotOptimized": 2,
"imagesOldFormat": 1,
"imagesScaledDown": 2,
"imagesExcessiveDensity": 0.25,
"imagesWithIncorrectSizesParam": 1,
"lazyLoadableImagesBelowTheFold": 2, "lazyLoadableImagesBelowTheFold": 2,
"hiddenImages": 1 "hiddenImages": 1
} }
@@ -101,17 +116,5 @@
"cachingTooShort": 1 "cachingTooShort": 1
} }
} }
},
"globalScore": {
"pageWeight": 3,
"requests": 2,
"domComplexity": 2,
"javascriptComplexity": 2,
"badJavascript": 2,
"jQuery": 0.5,
"cssComplexity": 0.5,
"badCSS": 1,
"fonts": 1,
"serverConfig": 1
} }
} }
+8 -1
View File
@@ -57,9 +57,16 @@ var RulesChecker = function() {
data.toolsResults[policy.tool].offenders[metricName]) { data.toolsResults[policy.tool].offenders[metricName]) {
offenders = data.toolsResults[policy.tool].offenders[metricName]; offenders = data.toolsResults[policy.tool].offenders[metricName];
} }
// It is possible to declare a transformation function for the main metric value.
// The function should
if (policy.valueTransformFn) {
rule.value = policy.valueTransformFn(offenders);
}
var offendersObj = {}; var offendersObj = {};
// It is possible to declare a transformation function for the offenders. // It is possible to declare a transformation function for the offenders.
// The function should take an array of strings as single parameter and return a string. // The function should take an array of strings as single parameter and return a string.
if (policy.offendersTransformFn) { if (policy.offendersTransformFn) {
+1 -1
View File
@@ -79,7 +79,7 @@ var Runner = function(params) {
}); });
// Fix: don't display Unicode ranges if the module is not present in Phantomas // Fix: don't display Unicode ranges if the module is not present in Phantomas
if (!data.toolsResults.phantomas.metrics.charactersCount) { if (!data.toolsResults.phantomas.metrics.differentCharacters) {
delete data.toolsResults.redownload.metrics.unusedUnicodeRanges; delete data.toolsResults.redownload.metrics.unusedUnicodeRanges;
delete data.toolsResults.redownload.offenders.unusedUnicodeRanges; delete data.toolsResults.redownload.offenders.unusedUnicodeRanges;
} }
+108
View File
@@ -0,0 +1,108 @@
var debug = require('debug')('ylt:screenshotHandlerAgent');
var Jimp = require('jimp');
var Q = require('q');
var fs = require('fs');
var path = require('path');
var screenshotHandler = function() {
this.findAndOptimizeScreenshot = function(tmpScreenshotPath, width) {
var that = this;
debug('Starting screenshot transformation');
return this.openImage(tmpScreenshotPath)
.then(function(image) {
that.deleteTmpFile(tmpScreenshotPath);
return that.resizeImage(image, width);
})
.then(this.toBuffer);
};
this.openImage = function(imagePath) {
var deferred = Q.defer();
Jimp.read(imagePath, function(err, image){
if (err) {
debug('Could not open imagePath %s', imagePath);
debug(err);
deferred.reject(err);
} else {
debug('Image correctly open');
deferred.resolve(image);
}
});
return deferred.promise;
};
this.resizeImage = function(image, newWidth) {
var deferred = Q.defer();
var currentWidth = image.bitmap.width;
if (currentWidth > 0) {
var ratio = newWidth / currentWidth;
image.scale(ratio, function(err, image){
if (err) {
debug('Could not resize image');
debug(err);
deferred.reject(err);
} else {
debug('Image correctly resized');
deferred.resolve(image);
}
});
} else {
deferred.reject('Could not resize an empty image');
}
return deferred.promise;
};
this.toBuffer = function(image) {
var deferred = Q.defer();
image.quality(85).getBuffer(Jimp.MIME_JPEG, function(err, buffer){
if (err) {
debug('Could not save image to buffer');
debug(err);
deferred.reject(err);
} else {
debug('Image correctly transformed to buffer');
deferred.resolve(buffer);
}
});
return deferred.promise;
};
this.deleteTmpFile = function(tmpFilePath) {
var deferred = Q.defer();
fs.unlink(tmpFilePath, function (err) {
if (err) {
debug('Screenshot temporary file not found, could not be deleted. But it is not a problem.');
} else {
debug('Screenshot temporary file deleted.');
}
deferred.resolve();
});
return deferred.promise;
};
};
module.exports = new screenshotHandler();
+9 -1
View File
@@ -1,4 +1,3 @@
var async = require('async');
var Q = require('q'); var Q = require('q');
var path = require('path'); var path = require('path');
var debug = require('debug')('ylt:phantomaswrapper'); var debug = require('debug')('ylt:phantomaswrapper');
@@ -43,9 +42,12 @@ var PhantomasWrapper = function() {
'block-domain': task.options.blockDomain, 'block-domain': task.options.blockDomain,
'allow-domain': task.options.allowDomain, 'allow-domain': task.options.allowDomain,
'no-externals': task.options.noExternals, 'no-externals': task.options.noExternals,
'local-storage': task.options.localStorage,
'session-storage': task.options.sessionStorage,
// Mandatory // Mandatory
'analyze-css': true, 'analyze-css': true,
'analyze-images': true,
'ignoreSslErrors': true, // until Phantomas 2.1 'ignoreSslErrors': true, // until Phantomas 2.1
'ignore-ssl-errors': true // for Phantomas >= 2.2 'ignore-ssl-errors': true // for Phantomas >= 2.2
}; };
@@ -79,6 +81,12 @@ var PhantomasWrapper = function() {
offenders: results.getAllOffenders() offenders: results.getAllOffenders()
}; };
// Special rules here
if (task.options.device !== 'phone') {
delete json.metrics.imagesExcessiveDensity;
delete json.offenders.imagesExcessiveDensity;
}
deferred.resolve(json); deferred.resolve(json);
}). }).
catch(res => { catch(res => {
+1 -1
View File
@@ -162,7 +162,7 @@ var FileMinifier = function() {
var deferred = Q.defer(); var deferred = Q.defer();
try { try {
var result = new CleanCSS({compatibility: 'ie8'}).minify(body); var result = new CleanCSS().minify(body);
deferred.resolve(result.styles); deferred.resolve(result.styles);
} catch(err) { } catch(err) {
deferred.reject(err); deferred.reject(err);
+4 -1
View File
@@ -204,7 +204,10 @@ var ImageOptimizer = function() {
} else if (type === 'png' && !lossy) { } else if (type === 'png' && !lossy) {
engine = imageminOptipng({optimizationLevel: OPTIPNG_COMPRESSION_LEVEL}); engine = imageminOptipng({optimizationLevel: OPTIPNG_COMPRESSION_LEVEL});
} else if (type === 'svg' && !lossy) { } else if (type === 'svg' && !lossy) {
engine = imageminSvgo({ plugins: [ { removeUselessDefs: false } ] }); engine = imageminSvgo({plugins: [{
name: 'preset-default',
params: {overrides: {removeUselessDefs: false}}
}]});
} else { } else {
deferred.reject('No optimization engine found for imagemin'); deferred.reject('No optimization engine found for imagemin');
} }
+23 -15
View File
@@ -14,13 +14,13 @@ var async = require('async');
var request = require('request'); var request = require('request');
var md5 = require('md5'); var md5 = require('md5');
var imageOptimizer = require('./imageOptimizer'); //var imageOptimizer = require('./imageOptimizer');
var fileMinifier = require('./fileMinifier'); var fileMinifier = require('./fileMinifier');
var gzipCompressor = require('./gzipCompressor'); var gzipCompressor = require('./gzipCompressor');
var brotliCompressor = require('./brotliCompressor'); var brotliCompressor = require('./brotliCompressor');
var contentTypeChecker = require('./contentTypeChecker'); var contentTypeChecker = require('./contentTypeChecker');
var fontAnalyzer = require('./fontAnalyzer'); var fontAnalyzer = require('./fontAnalyzer');
var imageDimensions = require('./imageDimensions'); //var imageDimensions = require('./imageDimensions');
var Redownload = function() { var Redownload = function() {
@@ -63,10 +63,9 @@ var Redownload = function() {
} }
} }
// Prevent a bug with the font analyzer on empty pages
var differentCharacters = ''; var differentCharacters = '';
if (data.toolsResults.phantomas.offenders.charactersCount && data.toolsResults.phantomas.offenders.charactersCount.length > 0) { if (data.toolsResults.phantomas.offenders.differentCharacters && data.toolsResults.phantomas.offenders.differentCharacters.length > 0) {
differentCharacters = data.toolsResults.phantomas.offenders.charactersCount[0]; differentCharacters = data.toolsResults.phantomas.offenders.differentCharacters[0];
} }
// Transform every request into a download function with a callback when done // Transform every request into a download function with a callback when done
@@ -77,9 +76,9 @@ var Redownload = function() {
.then(contentTypeChecker.checkContentType) .then(contentTypeChecker.checkContentType)
.then(imageOptimizer.optimizeImage) //.then(imageOptimizer.optimizeImage)
.then(imageDimensions.getDimensions) //.then(imageDimensions.getDimensions)
.then(fileMinifier.minifyFile) .then(fileMinifier.minifyFile)
@@ -152,12 +151,12 @@ var Redownload = function() {
}); });
// Image compression // Image compression
offenders.imageOptimization = listImagesNotOptimized(results); //offenders.imageOptimization = listImagesNotOptimized(results);
metrics.imageOptimization = offenders.imageOptimization.totalGain; //metrics.imageOptimization = offenders.imageOptimization.totalGain;
// Image width // Image width
offenders.imagesTooLarge = listImagesTooLarge(results, data.params.options.device); //offenders.imagesTooLarge = listImagesTooLarge(results, data.params.options.device);
metrics.imagesTooLarge = offenders.imagesTooLarge.length; //metrics.imagesTooLarge = offenders.imagesTooLarge.length;
// File minification // File minification
offenders.fileMinification = listFilesNotMinified(results); offenders.fileMinification = listFilesNotMinified(results);
@@ -195,6 +194,8 @@ var Redownload = function() {
offenders: offenders offenders: offenders
}; };
cleanResults(results);
deferred.resolve(data); deferred.resolve(data);
} }
}); });
@@ -340,7 +341,7 @@ var Redownload = function() {
} }
function listImagesNotOptimized(requests) { /*function listImagesNotOptimized(requests) {
var results = { var results = {
totalGain: 0, totalGain: 0,
images: [] images: []
@@ -395,9 +396,9 @@ var Redownload = function() {
} }
}); });
return results; return results;
} }*/
function listImagesTooLarge(requests, device) { /*function listImagesTooLarge(requests, device) {
var results = []; var results = [];
requests.forEach(function(req) { requests.forEach(function(req) {
@@ -422,7 +423,7 @@ var Redownload = function() {
}); });
return results; return results;
} }*/
function listFilesNotMinified(requests) { function listFilesNotMinified(requests) {
@@ -1010,6 +1011,13 @@ var Redownload = function() {
} }
} }
// Clean all the pollution this module added to the results
function cleanResults(requests) {
requests.forEach(function(req) {
delete req.weightCheck;
});
}
return { return {
recheckAllFiles: recheckAllFiles, recheckAllFiles: recheckAllFiles,
listRequestWeight: listRequestWeight, listRequestWeight: listRequestWeight,
+19 -17
View File
@@ -1,6 +1,6 @@
{ {
"name": "yellowlabtools", "name": "yellowlabtools",
"version": "2.1.0", "version": "3.0.0",
"description": "A tool that audits a webpage for performance and front-end quality issues", "description": "A tool that audits a webpage for performance and front-end quality issues",
"license": "GPL-2.0", "license": "GPL-2.0",
"author": { "author": {
@@ -16,24 +16,24 @@
"yellowlabtools": "./bin/cli.js" "yellowlabtools": "./bin/cli.js"
}, },
"engines": { "engines": {
"node": ">= 12.0" "node": ">= 18.0"
}, },
"main": "./lib/index.js", "main": "./lib/index.js",
"dependencies": { "dependencies": {
"async": "2.6.1", "async": "2.6.1",
"clean-css": "4.2.1", "clean-css": "4.2.1",
"color-diff": "1.1.0", "color-diff": "1.4.0",
"css-mq-parser": "0.0.3", "css-mq-parser": "0.0.3",
"debug": "4.1.1", "debug": "4.3.4",
"easyxml": "2.0.1", "easyxml": "2.0.1",
"fontkit": "1.7.8", "fontkit": "2.0.2",
"html-minifier": "4.0.0", "html-minifier": "4.0.0",
"image-size": "0.7.1", "image-size": "1.0.2",
"imagemin": "7.0.1", "imagemin": "7.0.1",
"imagemin-jpegoptim": "7.0.0", "imagemin-jpegoptim": "7.0.0",
"imagemin-jpegtran": "7.0.0", "imagemin-jpegtran": "7.0.0",
"imagemin-optipng": "8.0.0", "imagemin-optipng": "8.0.0",
"imagemin-svgo": "8.0.0", "imagemin-svgo": "9.0.0",
"is-eot": "1.0.0", "is-eot": "1.0.0",
"is-gif": "3.0.0", "is-gif": "3.0.0",
"is-jpg": "2.0.0", "is-jpg": "2.0.0",
@@ -45,24 +45,26 @@
"is-webp": "1.0.1", "is-webp": "1.0.1",
"is-woff": "1.0.3", "is-woff": "1.0.3",
"is-woff2": "1.0.0", "is-woff2": "1.0.0",
"md5": "2.2.1", "jimp": "0.22.8",
"md5": "2.3.0",
"meow": "5.0.0", "meow": "5.0.0",
"parse-color": "1.0.0", "parse-color": "1.0.0",
"phantomas": "2.2.0", "phantomas": "2.10.0",
"q": "1.5.1", "q": "1.5.1",
"request": "2.88.0", "request": "2.88.2",
"ttf2woff2": "4.0.1", "sharp": "0.32.6",
"uglify-js": "3.4.9", "ttf2woff2": "5.0.0",
"uglify-js": "3.17.4",
"woff-tools": "0.1.0" "woff-tools": "0.1.0"
}, },
"devDependencies": { "devDependencies": {
"chai": "~4.2.0", "chai": "~4.3.7",
"mocha": "~5.2.0", "mocha": "~10.2.0",
"sinon": "~7.2.3", "sinon": "~15.2.0",
"sinon-chai": "~3.3.0" "sinon-chai": "3.7.0"
}, },
"scripts": { "scripts": {
"test": "todo" "test": "mocha './test/core/**.js'"
}, },
"keywords": [ "keywords": [
"performance", "performance",
+2 -2
View File
@@ -49,7 +49,7 @@ describe('colorDiff', function() {
{url:'file.css', value: {message: '#5bc0de (2 times)'}}, {url:'file.css', value: {message: '#5bc0de (2 times)'}},
{url:'file.css', value: {message: 'rgba(0,0,0,0.075) (100 times)'}}, {url:'file.css', value: {message: 'rgba(0,0,0,0.075) (100 times)'}},
{url:'file.css', value: {message: 'rgb(91,192,222) (1000 times)'}}, {url:'file.css', value: {message: 'rgb(91,192,222) (1000 times)'}},
{url:'file.css', value: {message: 'rgba(0,0,2,1) (1 times)'}}, {url:'file.css', value: {message: 'rgba(0,0,1,1) (1 times)'}},
{url:'file.css', value: {message: 'rgba(99,99,99,1) (1 times)'}}, {url:'file.css', value: {message: 'rgba(99,99,99,1) (1 times)'}},
{url:'file.css', value: {message: 'rgba(100,100,100,1) (1 times)'}} {url:'file.css', value: {message: 'rgba(100,100,100,1) (1 times)'}}
]; ];
@@ -73,7 +73,7 @@ describe('colorDiff', function() {
newData.toolsResults.colorDiff.offenders.should.have.a.property('similarColors').that.deep.equals([ newData.toolsResults.colorDiff.offenders.should.have.a.property('similarColors').that.deep.equals([
{ {
color1: '#000', color1: '#000',
color2: 'rgba(0,0,2,1)', color2: 'rgba(0,0,1,1)',
isDark: true isDark: true
}, },
{ {
+10 -114
View File
@@ -76,114 +76,6 @@ describe('customPolicies', function() {
}); });
it('should transform DOMqueriesAvoidable offenders', function() {
results = rulesChecker.check({
"toolsResults": {
"phantomas": {
"metrics": {
"DOMqueriesAvoidable": 2
},
"offenders": {
"DOMqueriesDuplicated": [
"id \"#j2t-top-cart\" with getElementById (in context #document): 4 queries",
"class \".listingResult\" with getElementsByClassName (in context body > div#Global > div#Listing): 4 queries"
]
}
}
}
}, policies);
results.should.have.a.property('DOMqueriesAvoidable');
results.DOMqueriesAvoidable.should.have.a.property('offendersObj').that.deep.equals({
"count": 2,
"list": [
{
"query": "#j2t-top-cart",
"context": {
"type": "document"
},
"fn": "getElementById ",
"count": 4
},
{
"query": ".listingResult",
"context": {
"type": "domElement",
"element": "div#Listing",
"tree": {
"body": {
"div#Global": {
"div#Listing": 1
}
}
}
},
"fn": "getElementsByClassName ",
"count": 4
}
]
});
});
it('should transform jsErrors offenders', function() {
results = rulesChecker.check({
"toolsResults": {
"phantomas": {
"metrics": {
"jsErrors": 2
},
"offenders": {
"jsErrors": [
"TypeError: 'undefined' is not a function (evaluating 'this.successfullyCollected.bind(this)') - http://asset.easydmp.net/js/collect.js:1160 / callCollecte http://asset.easydmp.net/js/collect.js:1203 / callbackUpdateParams http://asset.easydmp.net/js/collect.js:1135 / http://asset.easydmp.net/js/collect.js:1191",
"TypeError: 'undefined' is not an object (evaluating 'd.readyState') - http://me.hunkal.com/p/:3"
]
}
}
}
}, policies);
results.should.have.a.property('jsErrors');
results.jsErrors.should.have.a.property('offendersObj').that.deep.equals({
"count": 2,
"list": [
{
"error": "TypeError: 'undefined' is not a function (evaluating 'this.successfullyCollected.bind(this)')",
"backtrace": [
{
"file": "http://asset.easydmp.net/js/collect.js",
"line": 1160
},
{
"file": "http://asset.easydmp.net/js/collect.js",
"line": 1203,
"functionName": "callCollecte"
},
{
"file": "http://asset.easydmp.net/js/collect.js",
"line": 1135,
"functionName": "callbackUpdateParams"
},
{
"file": "http://asset.easydmp.net/js/collect.js",
"line": 1191
}
]
},
{
"error": "TypeError: 'undefined' is not an object (evaluating 'd.readyState')",
"backtrace": [
{
"file": "http://me.hunkal.com/p/",
"line": 3
}
]
}
]
});
});
it('should grade correctly jQuery versions', function() { it('should grade correctly jQuery versions', function() {
var versions = { var versions = {
@@ -208,7 +100,8 @@ describe('customPolicies', function() {
"phantomas": { "phantomas": {
"metrics": { "metrics": {
"jQueryVersion": version "jQueryVersion": version
} },
"offenders": {}
} }
} }
}, policies); }, policies);
@@ -222,11 +115,12 @@ describe('customPolicies', function() {
"phantomas": { "phantomas": {
"metrics": { "metrics": {
"jQueryVersion": "wooot" "jQueryVersion": "wooot"
} },
"offenders": {}
} }
} }
}, policies); }, policies);
results.should.deep.equals({}); results.should.not.have.a.property('jQueryVersion');
// If jQueryVersionsLoaded is 0 // If jQueryVersionsLoaded is 0
@@ -236,7 +130,8 @@ describe('customPolicies', function() {
"metrics": { "metrics": {
"jQueryVersion": "1.6.0", "jQueryVersion": "1.6.0",
"jQueryVersionsLoaded": 0 "jQueryVersionsLoaded": 0
} },
"offenders": {}
} }
} }
}, policies); }, policies);
@@ -252,14 +147,15 @@ describe('customPolicies', function() {
"metrics": { "metrics": {
"jQueryVersion": "1.6.0", "jQueryVersion": "1.6.0",
"jQueryVersionsLoaded": 2 "jQueryVersionsLoaded": 2
} },
"offenders": {}
} }
} }
}, policies); }, policies);
results.should.not.have.a.property('jQueryVersion'); results.should.not.have.a.property('jQueryVersion');
results.should.have.a.property('jQueryVersionsLoaded'); results.should.have.a.property('jQueryVersionsLoaded');
results.jQueryVersionsLoaded.should.have.a.property('score').that.equals(0); results.jQueryVersionsLoaded.should.have.a.property('score').that.equals(0);
results.jQueryVersionsLoaded.should.have.a.property('abnormal').that.equals(true); results.jQueryVersionsLoaded.should.have.a.property('abnormal').that.equals(false);
}); });
+17 -9
View File
@@ -12,13 +12,13 @@ chai.use(sinonChai);
describe('index.js', function() { describe('index.js', function() {
it('should return a promise', function() { it('should return a promise', function() {
var promise = ylt(); /*var promise = ylt();
promise.should.have.property('then').that.is.a('function'); promise.should.have.property('then').that.is.a('function');
promise.should.have.property('fail').that.is.a('function'); promise.should.have.property('fail').that.is.a('function');*/
}); });
it('should fail an undefined url', function(done) { it('should fail with an undefined url', function(done) {
ylt().fail(function(err) { ylt().fail(function(err) {
err.should.be.a('string').that.equals('URL missing'); err.should.be.a('string').that.equals('URL missing');
done(); done();
@@ -50,7 +50,7 @@ describe('index.js', function() {
data.toolsResults.phantomas.should.be.an('object'); data.toolsResults.phantomas.should.be.an('object');
data.toolsResults.phantomas.should.have.a.property('url').that.equals(url); data.toolsResults.phantomas.should.have.a.property('url').that.equals(url);
data.toolsResults.phantomas.should.have.a.property('metrics').that.is.an('object'); data.toolsResults.phantomas.should.have.a.property('metrics').that.is.an('object');
data.toolsResults.phantomas.metrics.should.have.a.property('requests').that.equals(1); data.toolsResults.phantomas.metrics.should.have.a.property('requests').that.equals(2);
data.toolsResults.phantomas.should.have.a.property('offenders').that.is.an('object'); data.toolsResults.phantomas.should.have.a.property('offenders').that.is.an('object');
data.toolsResults.phantomas.offenders.should.have.a.property('DOMelementMaxDepth'); data.toolsResults.phantomas.offenders.should.have.a.property('DOMelementMaxDepth');
data.toolsResults.phantomas.offenders.DOMelementMaxDepth.should.have.length(2); data.toolsResults.phantomas.offenders.DOMelementMaxDepth.should.have.length(2);
@@ -66,13 +66,14 @@ describe('index.js', function() {
"tool": "phantomas", "tool": "phantomas",
"label": "DOM max depth", "label": "DOM max depth",
"message": "<p>A deep DOM makes the CSS matching with DOM elements difficult.</p><p>It also slows down JavaScript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for JavaScript events, that bubble up to the document root.</p>", "message": "<p>A deep DOM makes the CSS matching with DOM elements difficult.</p><p>It also slows down JavaScript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for JavaScript events, that bubble up to the document root.</p>",
"isOkThreshold": 12, "isOkThreshold": 15,
"isBadThreshold": 22, "isBadThreshold": 25,
"isAbnormalThreshold": 30, "isAbnormalThreshold": 32,
"hasOffenders": true "hasOffenders": true
}, },
"value": 1, "value": 1,
"bad": false, "bad": false,
"globalScoreIfFixed": 98,
"abnormal": false, "abnormal": false,
"score": 100, "score": 100,
"abnormalityScore": 0, "abnormalityScore": 0,
@@ -90,10 +91,10 @@ describe('index.js', function() {
/*jshint expr: true*/ /*jshint expr: true*/
console.log.should.not.have.been.called; console.log.should.not.have.been.called;
console.log.restore(); //console.log.restore();
done(); done();
}).fail(function(err) { }).fail(function(err) {
console.log.restore(); //console.log.restore();
done(err); done(err);
}); });
}); });
@@ -105,6 +106,7 @@ describe('index.js', function() {
ylt(url) ylt(url)
.then(function(data) { .then(function(data) {
console.log(data.toolsResults.phantomas.offenders.jsErrors);
data.toolsResults.phantomas.metrics.should.have.a.property('jsErrors').that.equals(0); data.toolsResults.phantomas.metrics.should.have.a.property('jsErrors').that.equals(0);
done(); done();
}).fail(function(err) { }).fail(function(err) {
@@ -119,6 +121,10 @@ describe('index.js', function() {
var url = 'http://localhost:8388/simple-page.html'; var url = 'http://localhost:8388/simple-page.html';
var screenshotPath = path.join(__dirname, '../../.tmp/indexTestScreenshot.png'); var screenshotPath = path.join(__dirname, '../../.tmp/indexTestScreenshot.png');
if (!fs.existsSync(path.join(__dirname, '../../.tmp'))){
fs.mkdirSync(path.join(__dirname, '../../.tmp'));
}
ylt(url, {screenshot: screenshotPath}) ylt(url, {screenshot: screenshotPath})
.then(function(data) { .then(function(data) {
@@ -130,6 +136,8 @@ describe('index.js', function() {
done(); done();
}).fail(function(err) { }).fail(function(err) {
done(err); done(err);
}).finally(function() {
}); });
}); });
-48
View File
@@ -1,48 +0,0 @@
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');
});
});
+5 -11
View File
@@ -82,16 +82,9 @@ describe('redownload', function() {
data.toolsResults.redownload.offenders.totalWeight.byType.image.requests.length.should.equal(2); data.toolsResults.redownload.offenders.totalWeight.byType.image.requests.length.should.equal(2);
data.toolsResults.redownload.offenders.totalWeight.byType.other.requests.length.should.equal(1); data.toolsResults.redownload.offenders.totalWeight.byType.other.requests.length.should.equal(1);
data.toolsResults.redownload.offenders.should.have.a.property('imageOptimization'); data.toolsResults.redownload.offenders.should.have.a.property('compression');
data.toolsResults.redownload.offenders.imageOptimization.totalGain.should.be.above(0); data.toolsResults.redownload.offenders.compression.totalGain.should.be.above(0);
data.toolsResults.redownload.offenders.imageOptimization.images.length.should.equal(2); data.toolsResults.redownload.offenders.compression.files.length.should.equal(5);
data.toolsResults.redownload.offenders.should.have.a.property('imagesTooLarge');
data.toolsResults.redownload.offenders.imagesTooLarge.length.should.equal(0);
data.toolsResults.redownload.offenders.should.have.a.property('gzipCompression');
data.toolsResults.redownload.offenders.gzipCompression.totalGain.should.be.above(0);
data.toolsResults.redownload.offenders.gzipCompression.files.length.should.equal(5);
data.toolsResults.redownload.offenders.should.have.a.property('fileMinification'); data.toolsResults.redownload.offenders.should.have.a.property('fileMinification');
data.toolsResults.redownload.offenders.fileMinification.totalGain.should.be.above(0); data.toolsResults.redownload.offenders.fileMinification.totalGain.should.be.above(0);
@@ -224,7 +217,8 @@ describe('redownload', function() {
}, },
status: 302, status: 302,
isHTML: true, isHTML: true,
contentLength: 999 contentLength: 999,
notFound: true
}; };
redownload.redownloadEntry(entry) redownload.redownloadEntry(entry)
+1
View File
@@ -9,6 +9,7 @@
document.getElementById(undefined); document.getElementById(undefined);
document.getElementsByClassName(undefined); document.getElementsByClassName(undefined);
document.getElementsByTagName(undefined); document.getElementsByTagName(undefined);
document.querySelector(undefined);
} catch(err) { } catch(err) {
console.log('Error found: ' + err); console.log('Error found: ' + err);