diffux-core 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|