percy-capybara 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/lib/percy/capybara.rb +1 -0
- data/lib/percy/capybara/client.rb +1 -0
- data/lib/percy/capybara/client/snapshots.rb +101 -37
- data/lib/percy/capybara/httpfetcher.rb +34 -0
- data/lib/percy/capybara/version.rb +1 -1
- data/spec/lib/percy/capybara/client/snapshots_spec.rb +135 -40
- data/spec/lib/percy/capybara/client/testdata/images/bg-relative-to-root.png +0 -0
- data/spec/lib/percy/capybara/client/testdata/images/bg-relative.png +0 -0
- data/spec/lib/percy/capybara/client/testdata/images/bg-stacked.png +0 -0
- data/spec/lib/percy/capybara/client/testdata/images/img-relative-to-root.png +0 -0
- data/spec/lib/percy/capybara/client/testdata/images/img-relative.png +0 -0
- data/spec/lib/percy/capybara/client/testdata/images/percy.svg +34 -0
- data/spec/lib/percy/capybara/client/testdata/images/srcset-base.png +0 -0
- data/spec/lib/percy/capybara/client/testdata/images/srcset-first.png +0 -0
- data/spec/lib/percy/capybara/client/testdata/images/srcset-second.png +0 -0
- data/spec/lib/percy/capybara/client/testdata/test-images.html +45 -0
- data/spec/lib/percy/capybara/httpfetcher_spec.rb +15 -0
- data/spec/lib/percy/capybara_spec.rb +2 -2
- data/spec/spec_helper.rb +3 -2
- metadata +25 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5897b9782db61c4a242df39bcb2f57124260d7c4
|
4
|
+
data.tar.gz: e38619b2dcc0b856fcc21963180aa187838b531a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb70c98eb8a3fdbaf66e75762cbbd6b5eef5e1e9eda8a05fd2bfc77e49cd76cfe20e33b478e9ca8c588ddc8fb4fefe5aa34f3513d58928cc0328351a53290513
|
7
|
+
data.tar.gz: 520bf4dadc5e701b0736a1b305b872c2766446995c046b36c2b58ec907b2000d2d90b8977ae05caea084d8d25cbfa4ff43c9a1cb27d792b45665828f2ecaf08b
|
data/.travis.yml
CHANGED
data/lib/percy/capybara.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'set'
|
1
2
|
require 'faraday'
|
2
3
|
require 'httpclient'
|
3
4
|
require 'digest'
|
@@ -6,9 +7,6 @@ module Percy
|
|
6
7
|
module Capybara
|
7
8
|
class Client
|
8
9
|
module Snapshots
|
9
|
-
# @private
|
10
|
-
FETCH_SENTINEL_VALUE = '[[FETCH]]'
|
11
|
-
|
12
10
|
# Takes a snapshot of the given page HTML and its assets.
|
13
11
|
#
|
14
12
|
# @param [Capybara::Session] page The Capybara page to snapshot.
|
@@ -35,6 +33,7 @@ module Percy
|
|
35
33
|
resources = []
|
36
34
|
resources << _get_root_html_resource(page)
|
37
35
|
resources += _get_css_resources(page)
|
36
|
+
resources += _get_image_resources(page)
|
38
37
|
resources.each { |resource| resource_map[resource.sha] = resource }
|
39
38
|
resource_map
|
40
39
|
end
|
@@ -61,75 +60,140 @@ module Percy
|
|
61
60
|
# Find all CSS resources.
|
62
61
|
# http://www.quirksmode.org/dom/w3c_css.html#access
|
63
62
|
script = <<-JS
|
64
|
-
function findStylesRecursively(stylesheet,
|
65
|
-
|
66
|
-
|
67
|
-
result_data[stylesheet.href] = result_data[stylesheet.href] || '';
|
63
|
+
function findStylesRecursively(stylesheet, css_urls) {
|
64
|
+
if (stylesheet.href) { // Skip stylesheet without hrefs (inline stylesheets).
|
65
|
+
css_urls.push(stylesheet.href);
|
68
66
|
|
69
67
|
// Remote stylesheet rules cannot be accessed because of the same-origin policy.
|
70
68
|
// Unfortunately, if you touch .cssRules in Selenium, it throws a JavascriptError
|
71
|
-
// with 'The operation is insecure'. To work around this, skip
|
72
|
-
//
|
69
|
+
// with 'The operation is insecure'. To work around this, skip reading rules of
|
70
|
+
// remote stylesheets but still include them for fetching.
|
71
|
+
//
|
72
|
+
// TODO: If a remote stylesheet has an @import, it will be missing because we don't
|
73
|
+
// notice it here. We could potentially recursively fetch remote imports in
|
74
|
+
// ruby-land below.
|
73
75
|
var parser = document.createElement('a');
|
74
76
|
parser.href = stylesheet.href;
|
75
77
|
if (parser.host != window.location.host) {
|
76
|
-
result_data[stylesheet.href] = '#{FETCH_SENTINEL_VALUE}'; // Must be a string.
|
77
78
|
return;
|
78
79
|
}
|
79
80
|
}
|
80
|
-
|
81
81
|
for (var i = 0; i < stylesheet.cssRules.length; i++) {
|
82
82
|
var rule = stylesheet.cssRules[i];
|
83
|
-
//
|
84
|
-
// These will be present in the HTML snapshot.
|
85
|
-
if (stylesheet.href) {
|
86
|
-
// Append current rule text.
|
87
|
-
result_data[stylesheet.href] += rule.cssText + '\\n';
|
88
|
-
}
|
89
|
-
|
90
|
-
// Handle recursive @imports.
|
83
|
+
// Depth-first search, handle recursive @imports.
|
91
84
|
if (rule.styleSheet) {
|
92
|
-
findStylesRecursively(rule.styleSheet,
|
85
|
+
findStylesRecursively(rule.styleSheet, css_urls);
|
93
86
|
}
|
94
87
|
}
|
95
88
|
}
|
96
89
|
|
97
|
-
var
|
90
|
+
var css_urls = [];
|
98
91
|
for (var i = 0; i < document.styleSheets.length; i++) {
|
99
|
-
findStylesRecursively(document.styleSheets[i],
|
92
|
+
findStylesRecursively(document.styleSheets[i], css_urls);
|
100
93
|
}
|
101
|
-
return
|
94
|
+
return css_urls;
|
102
95
|
JS
|
96
|
+
resource_urls = _evaluate_script(page, script)
|
97
|
+
|
98
|
+
resource_urls.each do |resource_url|
|
99
|
+
response = _fetch_resource_url(resource_url)
|
100
|
+
next if !response
|
101
|
+
sha = Digest::SHA256.hexdigest(response.body)
|
102
|
+
resources << Percy::Client::Resource.new(
|
103
|
+
sha, resource_url, mimetype: 'text/css', content: response.body)
|
104
|
+
end
|
105
|
+
resources
|
106
|
+
end
|
107
|
+
private :_get_css_resources
|
108
|
+
|
109
|
+
# @private
|
110
|
+
def _get_image_resources(page)
|
111
|
+
resources = []
|
112
|
+
image_urls = Set.new
|
113
|
+
|
114
|
+
# Find all image tags on the page.
|
115
|
+
page.all('img').each do |image_element|
|
116
|
+
srcs = []
|
117
|
+
srcs << image_element[:src] if !image_element[:src].nil?
|
103
118
|
|
104
|
-
|
105
|
-
|
119
|
+
srcset_raw_urls = image_element[:srcset] || ''
|
120
|
+
temp_urls = srcset_raw_urls.split(',')
|
121
|
+
temp_urls.each do |temp_url|
|
122
|
+
srcs << temp_url.split(' ').first
|
123
|
+
end
|
106
124
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
next if !response
|
112
|
-
css = response.body
|
125
|
+
srcs.each do |src|
|
126
|
+
# Skip data URIs.
|
127
|
+
next if src.match(/\Adata:/)
|
128
|
+
image_urls << src
|
113
129
|
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Find all CSS-loaded images which set a background-image.
|
133
|
+
script = <<-JS
|
134
|
+
var raw_image_urls = [];
|
114
135
|
|
115
|
-
|
136
|
+
var tags = document.getElementsByTagName('*');
|
137
|
+
var el;
|
138
|
+
var rawValue;
|
139
|
+
|
140
|
+
for (var i = 0; i < tags.length; i++) {
|
141
|
+
el = tags[i];
|
142
|
+
if (el.currentStyle) {
|
143
|
+
rawValue = el.currentStyle['backgroundImage'];
|
144
|
+
} else if (window.getComputedStyle) {
|
145
|
+
rawValue = window.getComputedStyle(el).getPropertyValue('background-image');
|
146
|
+
}
|
147
|
+
if (!rawValue || rawValue === "none") {
|
148
|
+
continue;
|
149
|
+
} else {
|
150
|
+
raw_image_urls.push(rawValue);
|
151
|
+
}
|
152
|
+
}
|
153
|
+
return raw_image_urls;
|
154
|
+
JS
|
155
|
+
raw_image_urls = _evaluate_script(page, script)
|
156
|
+
raw_image_urls.each do |raw_image_url|
|
157
|
+
temp_urls = raw_image_url.scan(/url\(["']?(.*?)["']?\)/)
|
158
|
+
# background-image can accept multiple url()s, so temp_urls is an array of URLs.
|
159
|
+
temp_urls.each do |temp_url|
|
160
|
+
# Skip data URIs.
|
161
|
+
next if temp_url[0].match(/\Adata:/)
|
162
|
+
image_urls << temp_url[0]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
image_urls.each do |image_url|
|
167
|
+
# Make the resource URL absolute to the current page. If it is already absolute, this
|
168
|
+
# will have no effect.
|
169
|
+
resource_url = URI.join(page.current_url, image_url).to_s
|
170
|
+
|
171
|
+
# Fetch the images.
|
172
|
+
# TODO(fotinakis): this can be pretty inefficient for image-heavy pages because the
|
173
|
+
# browser has already loaded them once and this fetch cannot easily leverage the
|
174
|
+
# browser's cache. However, often these images are probably local resources served by a
|
175
|
+
# development server, so it may not be so bad. Re-evaluate if this becomes an issue.
|
176
|
+
response = _fetch_resource_url(resource_url)
|
177
|
+
next if !response
|
178
|
+
|
179
|
+
sha = Digest::SHA256.hexdigest(response.body)
|
116
180
|
resources << Percy::Client::Resource.new(
|
117
|
-
sha, resource_url, mimetype:
|
181
|
+
sha, resource_url, mimetype: response.content_type, content: response.body)
|
118
182
|
end
|
119
183
|
resources
|
120
184
|
end
|
121
|
-
private :
|
185
|
+
private :_get_image_resources
|
122
186
|
|
123
187
|
# @private
|
124
188
|
def _fetch_resource_url(url)
|
125
|
-
response =
|
126
|
-
|
127
|
-
if response.status != 200
|
189
|
+
response = Percy::Capybara::HttpFetcher.fetch(url)
|
190
|
+
if !response
|
128
191
|
STDERR.puts "[percy] Warning: failed to fetch page resource, this might be a bug: #{url}"
|
129
192
|
return nil
|
130
193
|
end
|
131
194
|
response
|
132
195
|
end
|
196
|
+
private :_fetch_resource_url
|
133
197
|
|
134
198
|
# @private
|
135
199
|
def _evaluate_script(page, script)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Percy
|
4
|
+
module Capybara
|
5
|
+
module HttpFetcher
|
6
|
+
class Response < Struct.new(:body, :content_type); end
|
7
|
+
|
8
|
+
def self.fetch(url)
|
9
|
+
tempfile = Tempfile.new('percy-capybara-fetch')
|
10
|
+
temppath = tempfile.path
|
11
|
+
|
12
|
+
# Close and delete the tempfile, we just wanted the name. Also, we use the existence of the
|
13
|
+
# file as a signal below.
|
14
|
+
tempfile.close
|
15
|
+
tempfile.unlink
|
16
|
+
|
17
|
+
# Use curl as a magical subprocess weapon which escapes this Ruby sandbox and is not
|
18
|
+
# influenced by any HTTP middleware/restrictions. This helps us avoid causing lots of
|
19
|
+
# problems for people using gems like VCR/WebMock. We also disable certificate checking
|
20
|
+
# because, as odd as that is, it's the default state for Selenium Firefox and others.
|
21
|
+
output = `curl --insecure -v -o #{temppath} "#{url}" 2>&1`
|
22
|
+
content_type = output.match(/< Content-Type:(.*)/i)
|
23
|
+
content_type = content_type[1].strip if content_type
|
24
|
+
|
25
|
+
if File.exist?(temppath)
|
26
|
+
response = Percy::Capybara::HttpFetcher::Response.new(File.read(temppath), content_type)
|
27
|
+
# We've broken the tempfile so it won't get deleted when garbage collected. Delete!
|
28
|
+
File.delete(temppath)
|
29
|
+
response
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'digest'
|
3
3
|
|
4
|
-
RSpec.describe Percy::Capybara::Client::Snapshots do
|
4
|
+
RSpec.describe Percy::Capybara::Client::Snapshots, type: :feature do
|
5
5
|
let(:capybara_client) { Percy::Capybara::Client.new }
|
6
6
|
|
7
7
|
# Start a temp webserver that serves the testdata directory.
|
8
8
|
# You can test this server manually by running:
|
9
|
-
# ruby -run -e httpd spec/lib/percy/capybara/testdata -p 9090
|
9
|
+
# ruby -run -e httpd spec/lib/percy/capybara/client/testdata/ -p 9090
|
10
10
|
before(:all) do
|
11
11
|
port = get_random_open_port
|
12
12
|
Capybara.app_host = "http://localhost:#{port}"
|
@@ -19,10 +19,25 @@ RSpec.describe Percy::Capybara::Client::Snapshots do
|
|
19
19
|
].flatten)
|
20
20
|
|
21
21
|
# Block until the server is up.
|
22
|
+
WebMock.disable_net_connect!(allow_localhost: true)
|
22
23
|
verify_server_up(Capybara.app_host)
|
23
24
|
end
|
24
25
|
after(:all) { Process.kill('INT', @process.pid) }
|
25
26
|
|
27
|
+
before(:each) do
|
28
|
+
# Special setting for capybara-webkit. If clients are using capybara-webkit they would
|
29
|
+
# also have to have this setting enabled since apparently all resources are blocked by default.
|
30
|
+
page.driver.respond_to?(:allow_url) && page.driver.allow_url('*')
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_resource(resources, regex)
|
34
|
+
begin
|
35
|
+
resources.select { |resource| resource.resource_url.match(regex) }.fetch(0)
|
36
|
+
rescue IndexError
|
37
|
+
raise "Missing expected image with resource_url that matches: #{regex}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
26
41
|
describe '#_get_root_html_resource', type: :feature, js: true do
|
27
42
|
it 'includes the root DOM HTML' do
|
28
43
|
visit '/'
|
@@ -37,67 +52,141 @@ RSpec.describe Percy::Capybara::Client::Snapshots do
|
|
37
52
|
end
|
38
53
|
describe '#_get_css_resources', type: :feature, js: true do
|
39
54
|
it 'includes all linked and imported stylesheets' do
|
40
|
-
# For capybara-webkit.
|
41
|
-
page.driver.respond_to?(:allow_url) && page.driver.allow_url('maxcdn.bootstrapcdn.com')
|
42
|
-
|
43
55
|
visit '/test-css.html'
|
44
56
|
resources = capybara_client.send(:_get_css_resources, page)
|
45
57
|
|
46
|
-
|
47
|
-
expect(resources.collect(&:mimetype).uniq).to eq(['text/css'])
|
48
|
-
|
49
|
-
resource = resources.select do |resource|
|
50
|
-
resource.resource_url.match(/http:\/\/localhost:\d+\/css\/base\.css/)
|
51
|
-
end.fetch(0)
|
52
|
-
expect(resource.is_root).to be_falsey
|
58
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/css\/base\.css/)
|
53
59
|
|
54
60
|
expect(resource.content).to include('.colored-by-base { color: red; }')
|
55
61
|
expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
|
56
62
|
|
57
|
-
resource = resources
|
58
|
-
|
59
|
-
end.fetch(0)
|
60
|
-
expect(resource.is_root).to be_falsey
|
61
|
-
expect(resource.content).to include('@import url("imports.css")')
|
63
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/css\/simple-imports\.css/)
|
64
|
+
expect(resource.content).to include("@import url('imports.css');")
|
62
65
|
expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
|
63
66
|
|
64
|
-
resource = resources
|
65
|
-
resource.resource_url.match(/http:\/\/localhost:\d+\/css\/imports\.css/)
|
66
|
-
end.fetch(0)
|
67
|
-
expect(resource.is_root).to be_falsey
|
67
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/css\/imports\.css/)
|
68
68
|
expect(resource.content).to include('.colored-by-imports { color: red; }')
|
69
69
|
expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
|
70
70
|
|
71
|
-
resource = resources
|
72
|
-
|
73
|
-
end.fetch(0)
|
74
|
-
expect(resource.is_root).to be_falsey
|
75
|
-
expect(resource.content).to include('@import url("level1-imports.css")')
|
71
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/css\/level0-imports\.css/)
|
72
|
+
expect(resource.content).to include("@import url('level1-imports.css')")
|
76
73
|
expect(resource.content).to include('.colored-by-level0-imports { color: red; }')
|
77
74
|
expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
|
78
75
|
|
79
|
-
resource = resources
|
80
|
-
|
81
|
-
end.fetch(0)
|
82
|
-
expect(resource.is_root).to be_falsey
|
83
|
-
expect(resource.content).to include('@import url("level2-imports.css")')
|
76
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/css\/level1-imports\.css/)
|
77
|
+
expect(resource.content).to include("@import url('level2-imports.css')")
|
84
78
|
expect(resource.content).to include('.colored-by-level1-imports { color: red; }')
|
85
79
|
expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
|
86
80
|
|
87
|
-
resource = resources
|
88
|
-
resource.resource_url.match(/http:\/\/localhost:\d+\/css\/level2-imports\.css/)
|
89
|
-
end.fetch(0)
|
90
|
-
expect(resource.is_root).to be_falsey
|
81
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/css\/level2-imports\.css/)
|
91
82
|
expect(resource.content).to include(".colored-by-level2-imports { color: red; }")
|
92
83
|
expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
|
93
84
|
|
94
|
-
resource =
|
95
|
-
|
96
|
-
'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css')
|
97
|
-
end.fetch(0)
|
98
|
-
expect(resource.is_root).to be_falsey
|
85
|
+
resource = find_resource(
|
86
|
+
resources, /https:\/\/maxcdn.bootstrapcdn.com\/bootstrap\/3.3.4\/css\/bootstrap.min.css/)
|
99
87
|
expect(resource.content).to include('Bootstrap v3.3.4 (http://getbootstrap.com)')
|
100
88
|
expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
|
89
|
+
|
90
|
+
expect(resources.length).to eq(7)
|
91
|
+
expect(resources.collect(&:mimetype).uniq).to eq(['text/css'])
|
92
|
+
expect(resources.collect(&:is_root).uniq).to match_array([nil])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
describe '#_get_image_resources', type: :feature, js: true do
|
96
|
+
it 'includes all images' do
|
97
|
+
visit '/test-images.html'
|
98
|
+
resources = capybara_client.send(:_get_image_resources, page)
|
99
|
+
|
100
|
+
# The order of these is just for convenience, they match the order in test-images.html.
|
101
|
+
|
102
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/img-relative\.png/)
|
103
|
+
content = File.read(File.expand_path('../testdata/images/img-relative.png', __FILE__))
|
104
|
+
expect(resource.mimetype).to eq('image/png')
|
105
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
106
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
107
|
+
expect(resource.sha).to eq(expected_sha)
|
108
|
+
|
109
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/img-relative-to-root\.png/)
|
110
|
+
content = File.read(File.expand_path('../testdata/images/img-relative-to-root.png', __FILE__))
|
111
|
+
expect(resource.mimetype).to eq('image/png')
|
112
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
113
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
114
|
+
expect(resource.sha).to eq(expected_sha)
|
115
|
+
|
116
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/percy\.svg/)
|
117
|
+
content = File.read(File.expand_path('../testdata/images/percy.svg', __FILE__))
|
118
|
+
# In Ruby 1.9.3 the SVG mimetype is not registered so our mini ruby webserver doesn't serve
|
119
|
+
# the correct content type. Allow either to work here so we can test older Rubies fully.
|
120
|
+
expect(resource.mimetype).to match(/image\/svg\+xml|application\/octet-stream/)
|
121
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
122
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
123
|
+
expect(resource.sha).to eq(expected_sha)
|
124
|
+
|
125
|
+
resource = find_resource(resources, /http:\/\/i.imgur.com\/Umkjdao.png/)
|
126
|
+
content = Faraday.get('http://i.imgur.com/Umkjdao.png').body
|
127
|
+
expect(resource.mimetype).to eq('image/png')
|
128
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
129
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
130
|
+
expect(resource.sha).to eq(expected_sha)
|
131
|
+
|
132
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/bg-relative\.png/)
|
133
|
+
content = File.read(File.expand_path('../testdata/images/bg-relative.png', __FILE__))
|
134
|
+
expect(resource.mimetype).to eq('image/png')
|
135
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
136
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
137
|
+
expect(resource.sha).to eq(expected_sha)
|
138
|
+
|
139
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/bg-relative-to-root\.png/)
|
140
|
+
content = File.read(File.expand_path('../testdata/images/bg-relative-to-root.png', __FILE__))
|
141
|
+
expect(resource.mimetype).to eq('image/png')
|
142
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
143
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
144
|
+
expect(resource.sha).to eq(expected_sha)
|
145
|
+
|
146
|
+
resource = find_resource(resources, /http:\/\/i.imgur.com\/5mLoBs1.png/)
|
147
|
+
content = Faraday.get('http://i.imgur.com/5mLoBs1.png').body
|
148
|
+
expect(resource.mimetype).to eq('image/png')
|
149
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
150
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
151
|
+
expect(resource.sha).to eq(expected_sha)
|
152
|
+
|
153
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/bg-stacked\.png/)
|
154
|
+
content = File.read(File.expand_path('../testdata/images/bg-stacked.png', __FILE__))
|
155
|
+
expect(resource.mimetype).to eq('image/png')
|
156
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
157
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
158
|
+
expect(resource.sha).to eq(expected_sha)
|
159
|
+
|
160
|
+
resource = find_resource(resources, /http:\/\/i.imgur.com\/61AQuplb.jpg/)
|
161
|
+
content = Faraday.get('http://i.imgur.com/61AQuplb.jpg').body
|
162
|
+
expect(resource.mimetype).to eq('image/jpeg')
|
163
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
164
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
165
|
+
expect(resource.sha).to eq(expected_sha)
|
166
|
+
|
167
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/srcset-base\.png/)
|
168
|
+
content = File.read(File.expand_path('../testdata/images/srcset-base.png', __FILE__))
|
169
|
+
expect(resource.mimetype).to eq('image/png')
|
170
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
171
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
172
|
+
expect(resource.sha).to eq(expected_sha)
|
173
|
+
|
174
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/srcset-first\.png/)
|
175
|
+
content = File.read(File.expand_path('../testdata/images/srcset-first.png', __FILE__))
|
176
|
+
expect(resource.mimetype).to eq('image/png')
|
177
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
178
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
179
|
+
expect(resource.sha).to eq(expected_sha)
|
180
|
+
|
181
|
+
resource = find_resource(resources, /http:\/\/localhost:\d+\/images\/srcset-second\.png/)
|
182
|
+
content = File.read(File.expand_path('../testdata/images/srcset-second.png', __FILE__))
|
183
|
+
expect(resource.mimetype).to eq('image/png')
|
184
|
+
expected_sha = Digest::SHA256.hexdigest(content)
|
185
|
+
expect(Digest::SHA256.hexdigest(resource.content)).to eq(expected_sha)
|
186
|
+
expect(resource.sha).to eq(expected_sha)
|
187
|
+
|
188
|
+
expect(resources.length).to eq(12)
|
189
|
+
expect(resources.collect(&:is_root).uniq).to match_array([nil])
|
101
190
|
end
|
102
191
|
end
|
103
192
|
describe '#snapshot', type: :feature, js: true do
|
@@ -140,6 +229,12 @@ RSpec.describe Percy::Capybara::Client::Snapshots do
|
|
140
229
|
stub_request(:post, "https://percy.io/api/v1/builds/123/resources/")
|
141
230
|
.with(body: /#{resource.sha}/).to_return(status: 201, body: {success: true}.to_json)
|
142
231
|
|
232
|
+
expect(capybara_client).to receive(:_get_root_html_resource)
|
233
|
+
.with(page).once.and_call_original
|
234
|
+
expect(capybara_client).to receive(:_get_css_resources)
|
235
|
+
.with(page).once.and_call_original
|
236
|
+
expect(capybara_client).to receive(:_get_image_resources)
|
237
|
+
.with(page).once.and_call_original
|
143
238
|
resource_map = capybara_client.snapshot(page)
|
144
239
|
end
|
145
240
|
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
3
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
4
|
+
viewBox="0 0 170 110" enable-background="new 0 0 170 110" xml:space="preserve">
|
5
|
+
<g>
|
6
|
+
<g>
|
7
|
+
<polyline fill="#1F0924" points="49.2,97.6 9.4,103.4 46,86.8 6.2,81.2 46,75.6 9.4,59 49.2,64.9 18.7,38.6 55.2,55.4 33.4,21.7
|
8
|
+
63.7,48.1 52.2,9.6 73.9,43.4 73.7,3.2 85,41.8 96.2,3.2 96.1,43.4 117.7,9.5 106.3,48.1 136.5,21.6 114.7,55.4 151.2,38.6
|
9
|
+
120.8,64.8 160.6,59 124,75.6 163.8,81.1 124,86.8 160.6,103.3 120.8,97.5 "/>
|
10
|
+
<polyline fill="#EDDDC2" points="52.8,90.2 18.5,90.3 51.4,80.7 18.5,71.2 52.8,71.3 23.9,52.8 56.8,62.6 63,55.4 48.7,24.2
|
11
|
+
71.1,50.2 66.1,16.3 80.2,47.5 85,13.5 89.8,47.5 103.9,16.3 98.9,50.2 121.3,24.2 107,55.4 113.2,62.6 146.1,52.8 117.2,71.3
|
12
|
+
151.5,71.2 118.6,80.7 151.5,90.3 117.2,90.2 "/>
|
13
|
+
</g>
|
14
|
+
<polygon fill="#EDDDC2" points="108.3,45.7 134.4,39.1 127.9,65.2 "/>
|
15
|
+
<polygon fill="#1F0924" points="114.2,48.6 128.5,45 124.9,59.3 114.2,59.3 "/>
|
16
|
+
<polygon fill="#EDDDC2" points="61.7,45.7 35.6,39.1 42.1,65.2 "/>
|
17
|
+
<polygon fill="#1F0924" points="55.8,48.6 41.5,45 45.1,59.3 55.8,59.3 "/>
|
18
|
+
<polyline fill="#B157B5" points="38.3,101.1 131.7,101.1 166.8,88.5 131.5,76 155.8,47.6 119,54.5 125.9,17.7 97.5,42 85,6.7
|
19
|
+
72.5,42 44.1,17.7 51,54.5 14.2,47.6 38.5,76 3.2,88.5 "/>
|
20
|
+
<circle fill="#EDDDC2" cx="61.4" cy="78.7" r="12.1"/>
|
21
|
+
<circle fill="#1F0924" cx="61.4" cy="78.7" r="9.5"/>
|
22
|
+
<circle fill="#EDDDC2" cx="58.2" cy="75.5" r="4.2"/>
|
23
|
+
<circle fill="#EDDDC2" cx="108.6" cy="78.7" r="12.1"/>
|
24
|
+
<circle fill="#1F0924" cx="108.6" cy="78.7" r="9.5"/>
|
25
|
+
<circle fill="#EDDDC2" cx="105.3" cy="75.5" r="4.2"/>
|
26
|
+
<path fill="#EDDDC2" d="M62.9,97.6c0-12.2,9.9-22.1,22.1-22.1s22.1,9.9,22.1,22.1S62.9,109.8,62.9,97.6z"/>
|
27
|
+
<path fill="#1F0924" d="M89.8,96.5c-2,0-3.7-1-4.8-2.5c-1,1.5-2.8,2.5-4.8,2.5c-3.2,0-5.8-2.6-5.8-5.8c0-0.6,0.4-1,1-1s1,0.4,1,1
|
28
|
+
c0,2.1,1.7,3.8,3.8,3.8s3.8-1.7,3.8-3.8h2c0,2.1,1.7,3.8,3.8,3.8s3.8-1.7,3.8-3.8c0-0.6,0.4-1,1-1s1,0.4,1,1
|
29
|
+
C95.5,93.9,93,96.5,89.8,96.5z"/>
|
30
|
+
<path fill="#1F0924" d="M92.8,83.6c0,4.3-3.5,7.8-7.8,7.8s-7.8-3.5-7.8-7.8C77.2,79.3,92.8,79.3,92.8,83.6z"/>
|
31
|
+
<path fill="#EDDDC2" d="M47,101.3c0-4,3.2-7.3,7.3-7.3s7.3,3.2,7.3,7.3S47,105.3,47,101.3z"/>
|
32
|
+
<path fill="#EDDDC2" d="M108.4,101.3c0-4,3.2-7.3,7.3-7.3c4,0,7.3,3.2,7.3,7.3S108.4,105.3,108.4,101.3z"/>
|
33
|
+
</g>
|
34
|
+
</svg>
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<title>Test Percy::Capybara</title>
|
4
|
+
<h1>Test images</h1>
|
5
|
+
|
6
|
+
<h2>img local PNG (relative)</h2>
|
7
|
+
<img src="images/img-relative.png">
|
8
|
+
|
9
|
+
<h2>img local PNG (relative to root)</h2>
|
10
|
+
<img src="/images/img-relative-to-root.png">
|
11
|
+
|
12
|
+
<h2>img PNG (remote)</h2>
|
13
|
+
<img src="http://i.imgur.com/Umkjdao.png">
|
14
|
+
|
15
|
+
<h2>img SVG (relative)</h2>
|
16
|
+
<img src="../../../images/percy.svg" width="300">
|
17
|
+
|
18
|
+
<h2>CSS background-image (relative)</h2>
|
19
|
+
<div style="background-image: url(images/bg-relative.png); width: 200px; height: 200px;"></div>
|
20
|
+
|
21
|
+
<h2>CSS background-image (relative to root)</h2>
|
22
|
+
<div style="background-image: url(/images/bg-relative-to-root.png); width: 200px; height: 200px;"></div>
|
23
|
+
|
24
|
+
<h2>CSS background-image (remote)</h2>
|
25
|
+
<div style="background-image: url(http://i.imgur.com/5mLoBs1.png); width: 408px; height: 376px;"></div>
|
26
|
+
|
27
|
+
<h2>img data-uri (ignored)</h2>
|
28
|
+
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFVyxjAAAAmU4baAAAACNJREFUeNrswYEAAAAAw6D5U1/hAFUBAAAAAAAAAAAAjwkwACd0AAGLtJfaAAAAAElFTkSuQmCC">
|
29
|
+
|
30
|
+
<h2>CSS background-image data-uri (ignored)</h2>
|
31
|
+
<div style="background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFVyxjAAAAmU4baAAAACNJREFUeNrswYEAAAAAw6D5U1/hAFUBAAAAAAAAAAAAjwkwACd0AAGLtJfaAAAAAElFTkSuQmCC); width: 100px; height: 100px;"></div>
|
32
|
+
|
33
|
+
<h2>CSS background stacked (local and remote)</h2>
|
34
|
+
<div style="background: url(/images/bg-stacked.png) 200px 200px no-repeat, url('http://i.imgur.com/61AQuplb.jpg'); width: 100px; height: 100px;"></div>
|
35
|
+
|
36
|
+
<h2>img srcset</h2>
|
37
|
+
<img width="200" src="/images/srcset-base.png" srcset="/images/srcset-first.png 200w, /images/srcset-second.png 400w">
|
38
|
+
|
39
|
+
<h2>Duplicates of some of the above images, to verify they are not duplicated in resources:</h2>
|
40
|
+
<img src="images/img-relative.png">
|
41
|
+
<img src="/images/img-relative-to-root.png">
|
42
|
+
<img src="http://i.imgur.com/Umkjdao.png">
|
43
|
+
<div style="background-image: url(/images/bg-relative-to-root.png); width: 200px; height: 200px;"></div>
|
44
|
+
<div style="background-image: url(http://i.imgur.com/5mLoBs1.png); width: 408px; height: 376px;"></div>
|
45
|
+
</html>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
RSpec.describe Percy::Capybara::HttpFetcher do
|
2
|
+
it 'takes a URL and returns a response' do
|
3
|
+
response = Percy::Capybara::HttpFetcher.fetch('http://i.imgur.com/Umkjdao.png')
|
4
|
+
|
5
|
+
# Slightly magical hash, just a SHA-256 sum of the image above.
|
6
|
+
expect(Digest::SHA256.hexdigest(response.body)).to eq(
|
7
|
+
'4beb51550bef8e9e30d37ea8c13658e99bb01722062f218185e419af5ad93e13')
|
8
|
+
expect(response.content_type).to eq('image/png')
|
9
|
+
end
|
10
|
+
it 'returns nil if fetch failed' do
|
11
|
+
expect(Percy::Capybara::HttpFetcher.fetch('bad-url')).to be_nil
|
12
|
+
expect(Percy::Capybara::HttpFetcher.fetch('http://i.imgur.com/fake-image.png')).to be_nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -20,7 +20,7 @@ RSpec.describe Percy::Capybara do
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
describe '#snapshot' do
|
23
|
-
it '
|
23
|
+
it 'delegates to Percy::Capybara::Client' do
|
24
24
|
capybara_client = Percy::Capybara.capybara_client
|
25
25
|
expect(capybara_client).to receive(:initialize_build).once
|
26
26
|
Percy::Capybara.initialize_build
|
@@ -30,7 +30,7 @@ RSpec.describe Percy::Capybara do
|
|
30
30
|
it 'returns silently if no build is initialized' do
|
31
31
|
expect { Percy::Capybara.finalize_build }.to_not raise_error
|
32
32
|
end
|
33
|
-
it '
|
33
|
+
it 'delegates to Percy::Capybara::Client' do
|
34
34
|
capybara_client = Percy::Capybara.capybara_client
|
35
35
|
build_data = {'data' => {'id' => 123}}
|
36
36
|
expect(capybara_client.client).to receive(:create_build).and_return(build_data).once
|
data/spec/spec_helper.rb
CHANGED
@@ -35,7 +35,8 @@ RSpec.configure do |config|
|
|
35
35
|
# Comment this out to test the default Selenium/Firefox flow:
|
36
36
|
Capybara.javascript_driver = :webkit
|
37
37
|
|
38
|
-
config.before(:
|
39
|
-
WebMock.disable_net_connect!(allow_localhost: true
|
38
|
+
config.before(:each) do
|
39
|
+
WebMock.disable_net_connect!(allow_localhost: true)
|
40
40
|
end
|
41
|
+
config.before(:each, type: :feature) { WebMock.allow_net_connect! }
|
41
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: percy-capybara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Perceptual Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: percy-client
|
@@ -156,6 +156,7 @@ files:
|
|
156
156
|
- lib/percy/capybara/client.rb
|
157
157
|
- lib/percy/capybara/client/builds.rb
|
158
158
|
- lib/percy/capybara/client/snapshots.rb
|
159
|
+
- lib/percy/capybara/httpfetcher.rb
|
159
160
|
- lib/percy/capybara/version.rb
|
160
161
|
- percy-capybara.gemspec
|
161
162
|
- spec/lib/percy/capybara/client/builds_spec.rb
|
@@ -166,8 +167,19 @@ files:
|
|
166
167
|
- spec/lib/percy/capybara/client/testdata/css/level1-imports.css
|
167
168
|
- spec/lib/percy/capybara/client/testdata/css/level2-imports.css
|
168
169
|
- spec/lib/percy/capybara/client/testdata/css/simple-imports.css
|
170
|
+
- spec/lib/percy/capybara/client/testdata/images/bg-relative-to-root.png
|
171
|
+
- spec/lib/percy/capybara/client/testdata/images/bg-relative.png
|
172
|
+
- spec/lib/percy/capybara/client/testdata/images/bg-stacked.png
|
173
|
+
- spec/lib/percy/capybara/client/testdata/images/img-relative-to-root.png
|
174
|
+
- spec/lib/percy/capybara/client/testdata/images/img-relative.png
|
175
|
+
- spec/lib/percy/capybara/client/testdata/images/percy.svg
|
176
|
+
- spec/lib/percy/capybara/client/testdata/images/srcset-base.png
|
177
|
+
- spec/lib/percy/capybara/client/testdata/images/srcset-first.png
|
178
|
+
- spec/lib/percy/capybara/client/testdata/images/srcset-second.png
|
169
179
|
- spec/lib/percy/capybara/client/testdata/index.html
|
170
180
|
- spec/lib/percy/capybara/client/testdata/test-css.html
|
181
|
+
- spec/lib/percy/capybara/client/testdata/test-images.html
|
182
|
+
- spec/lib/percy/capybara/httpfetcher_spec.rb
|
171
183
|
- spec/lib/percy/capybara_spec.rb
|
172
184
|
- spec/spec_helper.rb
|
173
185
|
- spec/support/test_helpers.rb
|
@@ -204,8 +216,19 @@ test_files:
|
|
204
216
|
- spec/lib/percy/capybara/client/testdata/css/level1-imports.css
|
205
217
|
- spec/lib/percy/capybara/client/testdata/css/level2-imports.css
|
206
218
|
- spec/lib/percy/capybara/client/testdata/css/simple-imports.css
|
219
|
+
- spec/lib/percy/capybara/client/testdata/images/bg-relative-to-root.png
|
220
|
+
- spec/lib/percy/capybara/client/testdata/images/bg-relative.png
|
221
|
+
- spec/lib/percy/capybara/client/testdata/images/bg-stacked.png
|
222
|
+
- spec/lib/percy/capybara/client/testdata/images/img-relative-to-root.png
|
223
|
+
- spec/lib/percy/capybara/client/testdata/images/img-relative.png
|
224
|
+
- spec/lib/percy/capybara/client/testdata/images/percy.svg
|
225
|
+
- spec/lib/percy/capybara/client/testdata/images/srcset-base.png
|
226
|
+
- spec/lib/percy/capybara/client/testdata/images/srcset-first.png
|
227
|
+
- spec/lib/percy/capybara/client/testdata/images/srcset-second.png
|
207
228
|
- spec/lib/percy/capybara/client/testdata/index.html
|
208
229
|
- spec/lib/percy/capybara/client/testdata/test-css.html
|
230
|
+
- spec/lib/percy/capybara/client/testdata/test-images.html
|
231
|
+
- spec/lib/percy/capybara/httpfetcher_spec.rb
|
209
232
|
- spec/lib/percy/capybara_spec.rb
|
210
233
|
- spec/spec_helper.rb
|
211
234
|
- spec/support/test_helpers.rb
|