diffux-core 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/diffux-snapshot +8 -3
- data/lib/diffux_core/script/take-snapshot.js +170 -0
- data/lib/diffux_core/snapshot_comparison_image/base.rb +2 -10
- data/lib/diffux_core/snapshotter.rb +12 -3
- data/lib/diffux_core/version.rb +1 -1
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8362662564dad77a53ca282a389cbad52a5ed59c
|
4
|
+
data.tar.gz: 9ca2065b9f022ad5202f1a26e15c334b3bc2ea74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29e01c7448c6287d31f3b71ee141cbf08118b1c1d0262b74c503fe393e4f566dbdf03484caca80378971aa5fce09f033c0084a58d683300c7be63022710b1336
|
7
|
+
data.tar.gz: b4d1c26cc7e928ca91be9c1929c7e84cb473c42fb01ef476fa47b420e4ff2485590f6a40ef5995c79fd738f07d085ef381e219f2f9ed4d6305b3321ea10a76e2
|
data/bin/diffux-snapshot
CHANGED
@@ -26,6 +26,11 @@ OptionParser.new do |opts|
|
|
26
26
|
options[:width] = w
|
27
27
|
end
|
28
28
|
|
29
|
+
opts.on('-c', '--cropselector CROPSELECTOR',
|
30
|
+
'Limit the snapshot to a specific element') do |cs|
|
31
|
+
options[:crop_selector] = cs
|
32
|
+
end
|
33
|
+
|
29
34
|
opts.on('-a', '--useragent USERAGENT',
|
30
35
|
'Set a useragent header when loading the url') do |ua|
|
31
36
|
options[:useragent] = ua
|
@@ -47,10 +52,10 @@ unless options[:outfile]
|
|
47
52
|
exit 1
|
48
53
|
end
|
49
54
|
|
50
|
-
|
55
|
+
Diffux::Snapshotter.new(
|
51
56
|
viewport_width: options[:width],
|
57
|
+
crop_selector: options[:crop_selector],
|
52
58
|
user_agent: options[:useragent],
|
53
59
|
outfile: options[:outfile],
|
54
|
-
url: options[:url]
|
60
|
+
url: options[:url]
|
55
61
|
).take_snapshot!
|
56
|
-
|
@@ -0,0 +1,170 @@
|
|
1
|
+
var page = require('webpage').create(),
|
2
|
+
opts = JSON.parse(require('system').args[1]);
|
3
|
+
|
4
|
+
page.viewportSize = opts.viewportSize;
|
5
|
+
if (opts.userAgent) {
|
6
|
+
page.settings.userAgent = opts.userAgent;
|
7
|
+
} else {
|
8
|
+
page.settings.userAgent = page.settings.userAgent + ' Diffux';
|
9
|
+
}
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Configure timeouts
|
13
|
+
*/
|
14
|
+
page.waitTimeouts = {
|
15
|
+
initial: 1000,
|
16
|
+
afterResourceDone: 300,
|
17
|
+
fallback: 60000
|
18
|
+
};
|
19
|
+
// Don't wait more than 5 seconds on resources
|
20
|
+
page.settings.resourceTimeout = 5000;
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Saves a log string. The full log will be added to the console.log in the end,
|
24
|
+
* so that consumers of this script can use that information.
|
25
|
+
*/
|
26
|
+
page.allLogs = [];
|
27
|
+
page.log = function(string) {
|
28
|
+
page.allLogs.push(new Date().getTime() + ': ' + string);
|
29
|
+
};
|
30
|
+
|
31
|
+
/**
|
32
|
+
* By preventing animations from happening when we are taking the snapshots, we
|
33
|
+
* avoid timing issues that cause unwanted differences.
|
34
|
+
*/
|
35
|
+
page.preventAnimations = function() {
|
36
|
+
// CSS Transitions
|
37
|
+
var css = document.createElement('style');
|
38
|
+
css.type = 'text/css';
|
39
|
+
document.head.appendChild(css);
|
40
|
+
var sheet = css.sheet;
|
41
|
+
sheet.addRule('*', '-webkit-transition: none !important;');
|
42
|
+
sheet.addRule('*', 'transition: none !important;');
|
43
|
+
sheet.addRule('*', '-webkit-animation-duration: 0 !important;');
|
44
|
+
sheet.addRule('*', 'animation-duration: 0 !important;');
|
45
|
+
|
46
|
+
// jQuery
|
47
|
+
if (window.jQuery) {
|
48
|
+
jQuery.fx.off = true;
|
49
|
+
jQuery('*').stop(true, true);
|
50
|
+
}
|
51
|
+
|
52
|
+
// Prevent things like blinking cursors by un-focusing any focused
|
53
|
+
// elements
|
54
|
+
document.activeElement.blur();
|
55
|
+
};
|
56
|
+
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Waits until the page is ready and then fires a callback.
|
60
|
+
*
|
61
|
+
* This method will keep track of all resources requested (css, javascript, ajax
|
62
|
+
* requests, etc). As soon as we have no outstanding requests active, we start a
|
63
|
+
* short timer which fires the callback. If a new resource is requested in that
|
64
|
+
* short timeframe, we cancel the timer and wait for the new resource.
|
65
|
+
*
|
66
|
+
* In case something goes wrong, there's a 10 second fallback timer running in
|
67
|
+
* the background.
|
68
|
+
*/
|
69
|
+
page.waitUntilReady = function(callback) {
|
70
|
+
var fireCallback = function() {
|
71
|
+
page.log('Done - page is ready.');
|
72
|
+
clearTimeout(page.resourceWaitTimer);
|
73
|
+
clearTimeout(page.fallbackWaitTimer);
|
74
|
+
callback();
|
75
|
+
};
|
76
|
+
|
77
|
+
page.resourcesActive = [];
|
78
|
+
|
79
|
+
page.onResourceRequested = function(request) {
|
80
|
+
page.log('Ready: Request started - [' + request.id + '] ' + request.url);
|
81
|
+
page.log('Active requests - ' + page.resourcesActive);
|
82
|
+
if (page.resourceWaitTimer) {
|
83
|
+
page.log('Clearing timeout.');
|
84
|
+
clearTimeout(page.resourceWaitTimer);
|
85
|
+
page.resourceWaitTimer = null;
|
86
|
+
}
|
87
|
+
page.resourcesActive.push(request.id);
|
88
|
+
};
|
89
|
+
|
90
|
+
function onResourceEnded(response) {
|
91
|
+
page.log('Ready: Resource received - [' + response.id + '] '
|
92
|
+
+ response.url);
|
93
|
+
page.log('Active requests - ' + page.resourcesActive);
|
94
|
+
|
95
|
+
page.resourcesActive.splice(page.resourcesActive.indexOf(response.id), 1);
|
96
|
+
|
97
|
+
if (page.resourcesActive.length === 0) {
|
98
|
+
page.log('Potentially done, firing after short timeout.');
|
99
|
+
page.resourceWaitTimer = setTimeout(fireCallback,
|
100
|
+
page.waitTimeouts.afterResourceDone);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
|
105
|
+
page.onResourceError = onResourceEnded;
|
106
|
+
page.onResourceTimout = onResourceEnded;
|
107
|
+
page.onResourceReceived = function(response) {
|
108
|
+
if (response.stage === 'end') {
|
109
|
+
onResourceEnded(response);
|
110
|
+
}
|
111
|
+
};
|
112
|
+
|
113
|
+
page.log('Starting default timeouts. ' + JSON.stringify(page.waitTimeouts));
|
114
|
+
page.resourceWaitTimer = setTimeout(fireCallback, page.waitTimeouts.initial);
|
115
|
+
page.fallbackWaitTimer = setTimeout(fireCallback, page.waitTimeouts.fallback);
|
116
|
+
};
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Gets the top, left, width, and height of an element to crop and sets that to
|
120
|
+
* `page.clipRect` (a PhantomJS thing that will force `render` to only render
|
121
|
+
* that rectangle).
|
122
|
+
*/
|
123
|
+
page.cropOutElement = function() {
|
124
|
+
page.log('Cropping out ' + opts.cropSelector);
|
125
|
+
|
126
|
+
page.clipRect = page.evaluate(function(selector) {
|
127
|
+
var cropElement = document.querySelector(selector);
|
128
|
+
if (!cropElement) {
|
129
|
+
return;
|
130
|
+
}
|
131
|
+
return cropElement.getBoundingClientRect();
|
132
|
+
}, opts.cropSelector);
|
133
|
+
page.log('Crop set to ' + JSON.stringify(page.clipRect));
|
134
|
+
};
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Main place for taking the screenshot. Will exit the script when done.
|
138
|
+
*/
|
139
|
+
page.takeDiffuxSnapshot = function() {
|
140
|
+
// Try to prevent animations from running, to reduce variation in
|
141
|
+
// snapshots.
|
142
|
+
page.evaluate(page.preventAnimations);
|
143
|
+
|
144
|
+
// Check if the snapshot image should be cropped.
|
145
|
+
if (opts.cropSelector) {
|
146
|
+
page.cropOutElement();
|
147
|
+
}
|
148
|
+
// Save a PNG of the rendered page
|
149
|
+
page.render(opts.outfile);
|
150
|
+
|
151
|
+
// Capture metadata
|
152
|
+
var response = page.evaluate(function() {
|
153
|
+
return { title: document.title };
|
154
|
+
});
|
155
|
+
|
156
|
+
response.opts = opts;
|
157
|
+
response.log = page.allLogs.join('\n');
|
158
|
+
response.status = status;
|
159
|
+
|
160
|
+
// The phantomjs gem can read what is written to STDOUT which includes
|
161
|
+
// console.log, so we can use that to pass information from phantomjs back
|
162
|
+
// to the app.
|
163
|
+
console.log(JSON.stringify(response));
|
164
|
+
|
165
|
+
phantom.exit();
|
166
|
+
};
|
167
|
+
|
168
|
+
page.open(opts.address, function(status) {
|
169
|
+
page.waitUntilReady(page.takeDiffuxSnapshot);
|
170
|
+
});
|
@@ -66,10 +66,6 @@ module Diffux
|
|
66
66
|
# no default implementation
|
67
67
|
end
|
68
68
|
|
69
|
-
# Could be simplified as ChunkyPNG::Color::MAX * 2, but this format mirrors
|
70
|
-
# the math in #pixel_diff_score
|
71
|
-
MAX_EUCLIDEAN_DISTANCE = Math.sqrt(ChunkyPNG::Color::MAX**2 * 4)
|
72
|
-
|
73
69
|
# Compute a score that represents the difference between 2 pixels
|
74
70
|
#
|
75
71
|
# This method simply takes the Euclidean distance between the RGBA channels
|
@@ -86,12 +82,8 @@ module Diffux
|
|
86
82
|
# @return [Float] number between 0 and 1 where 1 is completely different
|
87
83
|
# and 0 is no difference
|
88
84
|
def pixel_diff_score(pixel_after, pixel_before)
|
89
|
-
|
90
|
-
|
91
|
-
(g(pixel_after) - g(pixel_before))**2 +
|
92
|
-
(b(pixel_after) - b(pixel_before))**2 +
|
93
|
-
(a(pixel_after) - a(pixel_before))**2
|
94
|
-
) / MAX_EUCLIDEAN_DISTANCE
|
85
|
+
ChunkyPNG::Color::euclidean_distance_rgba(pixel_after, pixel_before) /
|
86
|
+
ChunkyPNG::Color::MAX_EUCLIDEAN_DISTANCE_RGBA
|
95
87
|
end
|
96
88
|
|
97
89
|
# @param diff_score [Float]
|
@@ -8,7 +8,8 @@ module Diffux
|
|
8
8
|
# for a given URL and viewoprt, and then saving that snapshot to a file and
|
9
9
|
# storing any metadata on the Snapshot object.
|
10
10
|
class Snapshotter
|
11
|
-
SCRIPT_PATH = File.join(File.dirname(__FILE__),
|
11
|
+
SCRIPT_PATH = File.join(File.dirname(__FILE__),
|
12
|
+
'script/take-snapshot.js').to_s
|
12
13
|
|
13
14
|
# @param url [String} the URL to snapshot
|
14
15
|
# @param viewport_width [Integer] the width of the screen used when
|
@@ -16,15 +17,22 @@ module Diffux
|
|
16
17
|
# @param outfile [File] where to store the snapshot PNG.
|
17
18
|
# @param user_agent [String] an optional useragent string to used when
|
18
19
|
# requesting the page.
|
20
|
+
# @param crop_selector [String] an optional string containing a CSS
|
21
|
+
# selector. If this is present, and the page contains something matching
|
22
|
+
# it, the resulting snapshot image will only contain that element. If the
|
23
|
+
# page contains multiple elements mathing the selector, only the first
|
24
|
+
# element will be used.
|
19
25
|
def initialize(url: raise, viewport_width: raise,
|
20
|
-
outfile: raise, user_agent: nil
|
26
|
+
outfile: raise, user_agent: nil,
|
27
|
+
crop_selector: nil)
|
21
28
|
@viewport_width = viewport_width
|
29
|
+
@crop_selector = crop_selector
|
22
30
|
@user_agent = user_agent
|
23
31
|
@outfile = outfile
|
24
32
|
@url = url
|
25
33
|
end
|
26
34
|
|
27
|
-
# Takes a snapshot of the URL and saves it in the
|
35
|
+
# Takes a snapshot of the URL and saves it in the outfile as a PNG image.
|
28
36
|
#
|
29
37
|
# @return [Hash] a hash containing the following keys:
|
30
38
|
# title [String] the <title> of the page being snapshotted
|
@@ -34,6 +42,7 @@ module Diffux
|
|
34
42
|
opts = {
|
35
43
|
address: @url,
|
36
44
|
outfile: @outfile,
|
45
|
+
cropSelector: @crop_selector,
|
37
46
|
viewportSize: {
|
38
47
|
width: @viewport_width,
|
39
48
|
height: @viewport_width,
|
data/lib/diffux_core/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diffux-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Lencioni
|
@@ -9,8 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-05-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: chunky_png
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 1.3.1
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 1.3.1
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: oily_png
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,6 +95,20 @@ dependencies:
|
|
81
95
|
- - ">="
|
82
96
|
- !ruby/object:Gem::Version
|
83
97
|
version: 1.0.0
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: codeclimate-test-reporter
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
84
112
|
description: Tools for taking and comparing responsive website snapshots
|
85
113
|
email:
|
86
114
|
- joe.lencioni@causes.com
|
@@ -95,6 +123,7 @@ files:
|
|
95
123
|
- bin/diffux-snapshot
|
96
124
|
- lib/diffux_core.rb
|
97
125
|
- lib/diffux_core/diff_cluster_finder.rb
|
126
|
+
- lib/diffux_core/script/take-snapshot.js
|
98
127
|
- lib/diffux_core/snapshot_comparer.rb
|
99
128
|
- lib/diffux_core/snapshot_comparison_image/after.rb
|
100
129
|
- lib/diffux_core/snapshot_comparison_image/base.rb
|
@@ -128,3 +157,4 @@ signing_key:
|
|
128
157
|
specification_version: 4
|
129
158
|
summary: Diffux Core
|
130
159
|
test_files: []
|
160
|
+
has_rdoc:
|