percy-capybara 0.1.0 → 0.1.1
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/.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
|