percy-capybara 0.0.1 → 0.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c6f285faa3d3fa23666d4cc56279775e235e13e
4
- data.tar.gz: 5cf5533530d1083769131b3ace61b20f89083d07
3
+ metadata.gz: 19a9bfb3b76169ea2b1ba301f69e6466d2da4cef
4
+ data.tar.gz: 1f1963b73fa8ce3df14d48c34003cc631b5aa4a0
5
5
  SHA512:
6
- metadata.gz: 146c18609ebc74f87bbcd1a678da4924f738560c3e0d91c74e04d4243f1ee6fcc6585b67f728634ac88d00574099a7cb2f63b19ca1b6092aaff6bef43dc92417
7
- data.tar.gz: e589de3b4add655ff9c9d8c3b0d224fbf6f3adf4d4a06c79f5ef6cee63f323029b93547fe979edc60c63168fd7c8b53ddef98380ee34eb5abf1757cc16536006
6
+ metadata.gz: cccbace3dee7650f246cd96736e620719428ba22eaa88cec41f7aafa3564901d6af0271e77247a3a2b4f8f2758efb2e570bf33b9ea6f409138bb048af8e967b9
7
+ data.tar.gz: 99c6891ee227853322830ebff8648df6dbf7bc5fc11488151ed43c9e6a9065948c16faee2e82c8e9ad47d7f87863801d5a594c547e12ac120fba96cd5b4d4924
data/.gitignore CHANGED
@@ -1,35 +1,16 @@
1
- *.gem
2
- *.rbc
3
- /.config
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /.yardoc
4
+ /_yardoc/
4
5
  /coverage/
5
- /InstalledFiles
6
+ /doc/
6
7
  /pkg/
7
8
  /spec/reports/
8
- /test/tmp/
9
- /test/version_tmp/
10
9
  /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ *.rbc
15
+ mkmf.log
11
16
  .DS_Store
12
-
13
- ## Specific to RubyMotion:
14
- .dat*
15
- .repl_history
16
- build/
17
-
18
- ## Documentation cache and generated files:
19
- /.yardoc/
20
- /_yardoc/
21
- /doc/
22
- /rdoc/
23
-
24
- ## Environment normalisation:
25
- /.bundle/
26
- /lib/bundler/man/
27
-
28
- # for a library or gem, you might want to ignore these files since the code is
29
- # intended to run in multiple environments; otherwise, check them in:
30
- # Gemfile.lock
31
- # .ruby-version
32
- # .ruby-gemset
33
-
34
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
- .rvmrc
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --require spec_helper
3
+ --format d
4
+
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.1.1
5
+ - 2.2.2
6
+ - ruby-head
7
+ script: bundle exec rspec
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --protected
2
+ --no-private
data/Gemfile CHANGED
@@ -1,4 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in perceptual.gemspec
3
+ # Specify your gem's dependencies in percy-capybara.gemspec
4
4
  gemspec
5
+
6
+ gem 'guard-rspec', require: false
7
+
8
+ # (for development)
9
+ # gem 'percy-client', path: '~/src/percy-client'
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ require 'guard/rspec/dsl'
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # RSpec files
6
+ rspec = dsl.rspec
7
+ watch(rspec.spec_helper) { rspec.spec_dir }
8
+ watch(rspec.spec_support) { rspec.spec_dir }
9
+ watch(rspec.spec_files)
10
+
11
+ # Ruby files
12
+ ruby = dsl.ruby
13
+ dsl.watch_spec_files_for(ruby.lib_files)
14
+ end
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
- The MIT License (MIT)
1
+ Copyright (c) 2015 Perceptual Inc.
2
2
 
3
- Copyright (c) 2015 Mike Fotinakis
3
+ The MIT License (MIT)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,2 +1,34 @@
1
- # perceptual-ruby
2
- Ruby client library for Perceptual CI
1
+ # Percy::Capybara
2
+
3
+ [![Build Status](https://travis-ci.org/percy/percy-capybara.svg?branch=master)](https://travis-ci.org/percy/percy-capybara)
4
+ [![Gem Version](https://badge.fury.io/rb/percy-capybara.svg)](http://badge.fury.io/rb/percy-capybara)
5
+
6
+ (in development, coming soon)
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'percy-capybara'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install percy-capybara
23
+
24
+ ## Usage
25
+
26
+ ...
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it ( https://github.com/percy/percy-capybara/fork )
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create a new Pull Request
data/Rakefile CHANGED
@@ -1,2 +1,2 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
 
@@ -0,0 +1,47 @@
1
+ require 'percy'
2
+ require 'percy/capybara/version'
3
+ require 'percy/capybara/client'
4
+
5
+ module Percy
6
+ module Capybara
7
+ # @see Percy::Capybara::Client
8
+ def self.capybara_client
9
+ @capybara_client ||= Percy::Capybara::Client.new
10
+ end
11
+
12
+ # {include:Percy::Capybara::Client::Snapshots#snapshot}
13
+ # @param (see Percy::Capybara::Client::Snapshots#snapshot)
14
+ # @option (see Percy::Capybara::Client::Snapshots#snapshot)
15
+ # @see Percy::Capybara::Client::Snapshots#snapshot
16
+ def self.snapshot(page, options = {})
17
+ capybara_client.snapshot(page, options)
18
+ end
19
+
20
+ # Creates a new build.
21
+ #
22
+ # This usually does not need to be called explictly because the build is automatically created
23
+ # the first time a snapshot is created. However, this method might be useful in situations like
24
+ # multi-process tests where a single build must be created before forking.
25
+ #
26
+ # @see Percy::Capybara::Client::Builds#initialize_build
27
+ def self.initialize_build
28
+ capybara_client.initialize_build
29
+ end
30
+
31
+ # Finalize the current build.
32
+ #
33
+ # This must be called to indicate that the build is complete after all snapshots have been
34
+ # taken. It will silently return if no build or snapshots were created.
35
+ #
36
+ # @see Percy::Capybara::Client::Builds#finalize_current_build
37
+ def self.finalize_build
38
+ return if !capybara_client.build_initialized?
39
+ capybara_client.finalize_current_build
40
+ end
41
+
42
+ # Reset the global Percy::Capybara module state.
43
+ def self.reset
44
+ @capybara_client = nil
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,20 @@
1
+ require 'percy/capybara/client/builds'
2
+ require 'percy/capybara/client/snapshots'
3
+
4
+ module Percy
5
+ module Capybara
6
+ class Client
7
+ include Percy::Capybara::Client::Builds
8
+ include Percy::Capybara::Client::Snapshots
9
+
10
+ class Error < Exception; end
11
+ class BuildNotInitializedError < Error; end
12
+
13
+ attr_reader :client
14
+
15
+ def initialize(options = {})
16
+ @client = options[:client] || Percy.client
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ module Percy
2
+ module Capybara
3
+ class Client
4
+ module Builds
5
+ def current_build
6
+ @current_build ||= client.create_build(client.config.repo)
7
+ end
8
+ alias_method :initialize_build, :current_build
9
+
10
+ def build_initialized?
11
+ !!@current_build
12
+ end
13
+
14
+ def finalize_current_build
15
+ if !build_initialized?
16
+ raise Percy::Capybara::Client::BuildNotInitializedError.new(
17
+ 'Failed to finalize build because no build has been initialized.')
18
+ end
19
+ client.finalize_build(current_build['data']['id'])
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,147 @@
1
+ require 'faraday'
2
+ require 'httpclient'
3
+ require 'digest'
4
+
5
+ module Percy
6
+ module Capybara
7
+ class Client
8
+ module Snapshots
9
+ # @private
10
+ FETCH_SENTINEL_VALUE = '[[FETCH]]'
11
+
12
+ # Takes a snapshot of the given page HTML and its assets.
13
+ #
14
+ # @param [Capybara::Session] page The Capybara page to snapshot.
15
+ # @param [Hash] options
16
+ # @option options [String] :name A unique name for the current page that identifies it across
17
+ # builds. By default this is the URL of the page, but can be customized if the URL does not
18
+ # entirely identify the current state.
19
+ def snapshot(page, options = {})
20
+ name = options[:name]
21
+ current_build_id = current_build['data']['id']
22
+ resource_map = _find_resources(page)
23
+ snapshot = client.create_snapshot(current_build_id, resource_map.values, name: name)
24
+
25
+ # Upload the content for any missing resources.
26
+ snapshot['data']['links']['missing-resources']['linkage'].each do |missing_resource|
27
+ sha = missing_resource['id']
28
+ client.upload_resource(current_build_id, resource_map[sha].content)
29
+ end
30
+ end
31
+
32
+ # @private
33
+ def _find_resources(page)
34
+ resource_map = {}
35
+ resources = []
36
+ resources << _get_root_html_resource(page)
37
+ resources += _get_css_resources(page)
38
+ resources.each { |resource| resource_map[resource.sha] = resource }
39
+ resource_map
40
+ end
41
+ private :_find_resources
42
+
43
+ # @private
44
+ def _get_root_html_resource(page)
45
+ # Primary HTML.
46
+ script = <<-JS
47
+ var htmlElement = document.getElementsByTagName('html')[0];
48
+ return htmlElement.outerHTML;
49
+ JS
50
+ html = _evaluate_script(page, script)
51
+ sha = Digest::SHA256.hexdigest(html)
52
+ resource_url = page.current_url
53
+ Percy::Client::Resource.new(
54
+ sha, resource_url, is_root: true, mimetype: 'text/html', content: html)
55
+ end
56
+ private :_get_root_html_resource
57
+
58
+ # @private
59
+ def _get_css_resources(page)
60
+ resources = []
61
+ # Find all CSS resources.
62
+ # http://www.quirksmode.org/dom/w3c_css.html#access
63
+ script = <<-JS
64
+ function findStylesRecursively(stylesheet, result_data) {
65
+ result_data = result_data || {};
66
+ if (stylesheet.href) {
67
+ result_data[stylesheet.href] = result_data[stylesheet.href] || '';
68
+
69
+ // Remote stylesheet rules cannot be accessed because of the same-origin policy.
70
+ // Unfortunately, if you touch .cssRules in Selenium, it throws a JavascriptError
71
+ // with 'The operation is insecure'. To work around this, skip any remote stylesheets
72
+ // and mark them with a sentinel value so we can fetch them later.
73
+ var parser = document.createElement('a');
74
+ parser.href = stylesheet.href;
75
+ if (parser.host != window.location.host) {
76
+ result_data[stylesheet.href] = '#{FETCH_SENTINEL_VALUE}'; // Must be a string.
77
+ return;
78
+ }
79
+ }
80
+
81
+ for (var i = 0; i < stylesheet.cssRules.length; i++) {
82
+ var rule = stylesheet.cssRules[i];
83
+ // Skip stylesheet without hrefs (inline stylesheets).
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.
91
+ if (rule.styleSheet) {
92
+ findStylesRecursively(rule.styleSheet, result_data);
93
+ }
94
+ }
95
+ }
96
+
97
+ var percy_resources = {};
98
+ for (var i = 0; i < document.styleSheets.length; i++) {
99
+ findStylesRecursively(document.styleSheets[i], percy_resources);
100
+ }
101
+ return percy_resources;
102
+ JS
103
+
104
+ # Returned datastructure: {"<absolute URL>" => "<CSS text>", ...}
105
+ resource_data = _evaluate_script(page, script)
106
+
107
+ resource_data.each do |resource_url, css|
108
+ if css == FETCH_SENTINEL_VALUE
109
+ # Handle sentinel value that indicates a remote CSS resource that must be fetched.
110
+ response = _fetch_resource_url(resource_url)
111
+ next if !response
112
+ css = response.body
113
+ end
114
+
115
+ sha = Digest::SHA256.hexdigest(css)
116
+ resources << Percy::Client::Resource.new(
117
+ sha, resource_url, mimetype: 'text/css', content: css)
118
+ end
119
+ resources
120
+ end
121
+ private :_get_css_resources
122
+
123
+ # @private
124
+ def _fetch_resource_url(url)
125
+ response = Faraday.get(url)
126
+ content = response.body
127
+ if response.status != 200
128
+ STDERR.puts "[percy] Warning: failed to fetch page resource, this might be a bug: #{url}"
129
+ return nil
130
+ end
131
+ response
132
+ end
133
+
134
+ # @private
135
+ def _evaluate_script(page, script)
136
+ script = <<-JS
137
+ (function() {
138
+ #{script}
139
+ })();
140
+ JS
141
+ page.evaluate_script(script)
142
+ end
143
+ private :_evaluate_script
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,5 @@
1
+ module Percy
2
+ module Capybara
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'percy/capybara/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'percy-capybara'
8
+ spec.version = Percy::Capybara::VERSION
9
+ spec.authors = ['Perceptual Inc.']
10
+ spec.email = ['team@percy.io']
11
+ spec.summary = %q{Percy::Capybara}
12
+ spec.description = %q{}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'percy-client', '~> 0.1'
22
+ spec.add_dependency 'faraday'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.7'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.2'
27
+ spec.add_development_dependency 'capybara', '~> 2.4'
28
+ spec.add_development_dependency 'capybara-webkit', '~> 1.5'
29
+ spec.add_development_dependency 'selenium-webdriver'
30
+ spec.add_development_dependency 'webmock', '~> 1'
31
+ end
@@ -0,0 +1,42 @@
1
+ RSpec.describe Percy::Capybara::Client::Builds do
2
+ let(:capybara_client) { Percy::Capybara::Client.new }
3
+
4
+ describe '#current_build' do
5
+ it 'returns the current build or creates a new one' do
6
+ mock_double = double('build')
7
+ expect(capybara_client.client).to receive(:create_build)
8
+ .with(capybara_client.client.config.repo)
9
+ .and_return(mock_double)
10
+ .once
11
+
12
+ current_build = capybara_client.current_build
13
+ expect(current_build).to eq(mock_double)
14
+ # Verify that it memoizes the current build by calling it again:
15
+ expect(current_build).to eq(mock_double)
16
+ end
17
+ end
18
+ describe '#build_initialized?' do
19
+ it 'is false before a build is initialized and true afterward' do
20
+ expect(capybara_client.client).to receive(:create_build).and_return(double('build'))
21
+ expect(capybara_client.build_initialized?).to be_falsey
22
+
23
+ capybara_client.initialize_build
24
+ expect(capybara_client.build_initialized?).to be_truthy
25
+ end
26
+ end
27
+ describe '#finalize_current_build' do
28
+ it 'finalizes the current build' do
29
+ build_data = {'data' => {'id' => 123}}
30
+ expect(capybara_client.client).to receive(:create_build).and_return(build_data)
31
+ capybara_client.initialize_build
32
+
33
+ expect(capybara_client.client).to receive(:finalize_build).with(123)
34
+ capybara_client.finalize_current_build
35
+ end
36
+ it 'raises an error if no current build exists' do
37
+ expect do
38
+ capybara_client.finalize_current_build
39
+ end.to raise_error(Percy::Capybara::Client::BuildNotInitializedError)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,147 @@
1
+ require 'json'
2
+ require 'digest'
3
+
4
+ RSpec.describe Percy::Capybara::Client::Snapshots do
5
+ let(:capybara_client) { Percy::Capybara::Client.new }
6
+
7
+ # Start a temp webserver that serves the testdata directory.
8
+ # You can test this server manually by running:
9
+ # ruby -run -e httpd spec/lib/percy/capybara/testdata -p 9090
10
+ before(:all) do
11
+ port = get_random_open_port
12
+ Capybara.app_host = "http://localhost:#{port}"
13
+ Capybara.run_server = false
14
+
15
+ # Note: using this form of popen to keep stdout and stderr silent and captured.
16
+ dir = File.expand_path('../testdata/', __FILE__)
17
+ @process = IO.popen([
18
+ 'ruby', '-run', '-e', 'httpd', dir, '-p', port.to_s, err: [:child, :out]
19
+ ].flatten)
20
+
21
+ # Block until the server is up.
22
+ verify_server_up(Capybara.app_host)
23
+ end
24
+ after(:all) { Process.kill('INT', @process.pid) }
25
+
26
+ describe '#_get_root_html_resource', type: :feature, js: true do
27
+ it 'includes the root DOM HTML' do
28
+ visit '/'
29
+ resource = capybara_client.send(:_get_root_html_resource, page)
30
+
31
+ expect(resource.is_root).to be_truthy
32
+ expect(resource.mimetype).to eq('text/html')
33
+ expect(resource.resource_url).to match(/http:\/\/localhost:\d+\//)
34
+ expect(resource.content).to include('Hello World!')
35
+ expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
36
+ end
37
+ end
38
+ describe '#_get_css_resources', type: :feature, js: true do
39
+ 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
+ visit '/test-css.html'
44
+ resources = capybara_client.send(:_get_css_resources, page)
45
+
46
+ expect(resources.length).to eq(7)
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
53
+
54
+ expect(resource.content).to include('.colored-by-base { color: red; }')
55
+ expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
56
+
57
+ resource = resources.select do |resource|
58
+ resource.resource_url.match(/http:\/\/localhost:\d+\/css\/simple-imports\.css/)
59
+ end.fetch(0)
60
+ expect(resource.is_root).to be_falsey
61
+ expect(resource.content).to include('@import url("imports.css")')
62
+ expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
63
+
64
+ resource = resources.select do |resource|
65
+ resource.resource_url.match(/http:\/\/localhost:\d+\/css\/imports\.css/)
66
+ end.fetch(0)
67
+ expect(resource.is_root).to be_falsey
68
+ expect(resource.content).to include('.colored-by-imports { color: red; }')
69
+ expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
70
+
71
+ resource = resources.select do |resource|
72
+ resource.resource_url.match(/http:\/\/localhost:\d+\/css\/level0-imports\.css/)
73
+ end.fetch(0)
74
+ expect(resource.is_root).to be_falsey
75
+ expect(resource.content).to include('@import url("level1-imports.css")')
76
+ expect(resource.content).to include('.colored-by-level0-imports { color: red; }')
77
+ expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
78
+
79
+ resource = resources.select do |resource|
80
+ resource.resource_url.match(/http:\/\/localhost:\d+\/css\/level1-imports\.css/)
81
+ end.fetch(0)
82
+ expect(resource.is_root).to be_falsey
83
+ expect(resource.content).to include('@import url("level2-imports.css")')
84
+ expect(resource.content).to include('.colored-by-level1-imports { color: red; }')
85
+ expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
86
+
87
+ resource = resources.select do |resource|
88
+ resource.resource_url.match(/http:\/\/localhost:\d+\/css\/level2-imports\.css/)
89
+ end.fetch(0)
90
+ expect(resource.is_root).to be_falsey
91
+ expect(resource.content).to include(".colored-by-level2-imports { color: red; }")
92
+ expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
93
+
94
+ resource = resources.select do |resource|
95
+ resource.resource_url == (
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
99
+ expect(resource.content).to include('Bootstrap v3.3.4 (http://getbootstrap.com)')
100
+ expect(resource.sha).to eq(Digest::SHA256.hexdigest(resource.content))
101
+ end
102
+ end
103
+ describe '#snapshot', type: :feature, js: true do
104
+ context 'simple page with no resources' do
105
+ let(:content) { '<html><body>Hello World!</body><head></head></html>' }
106
+
107
+ it 'creates a snapshot and uploads missing resource' do
108
+ visit '/'
109
+
110
+ mock_response = {
111
+ 'data' => {
112
+ 'id' => '123',
113
+ 'type' => 'builds',
114
+ },
115
+ }
116
+ stub_request(:post, 'https://percy.io/api/v1/repos/percy/percy-capybara/builds/')
117
+ .to_return(status: 201, body: mock_response.to_json)
118
+
119
+ resource = capybara_client.send(:_get_root_html_resource, page)
120
+ mock_response = {
121
+ 'data' => {
122
+ 'id' => '256',
123
+ 'type' => 'snapshots',
124
+ 'links' => {
125
+ 'self' => "/api/v1/snapshots/123",
126
+ 'missing-resources' => {
127
+ 'linkage' => [
128
+ {
129
+ 'type' => 'resources',
130
+ 'id' => resource.sha,
131
+ },
132
+ ],
133
+ },
134
+ },
135
+ },
136
+ }
137
+ stub_request(:post, 'https://percy.io/api/v1/builds/123/snapshots/')
138
+ .to_return(status: 201, body: mock_response.to_json)
139
+
140
+ stub_request(:post, "https://percy.io/api/v1/builds/123/resources/")
141
+ .with(body: /#{resource.sha}/).to_return(status: 201, body: {success: true}.to_json)
142
+
143
+ resource_map = capybara_client.snapshot(page)
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1 @@
1
+ .colored-by-base { color: red; }
@@ -0,0 +1 @@
1
+ .colored-by-imports { color: red; }
@@ -0,0 +1,2 @@
1
+ @import url('level1-imports.css');
2
+ .colored-by-level0-imports { color: red; }
@@ -0,0 +1,2 @@
1
+ @import url('level2-imports.css');
2
+ .colored-by-level1-imports { color: red; }
@@ -0,0 +1 @@
1
+ .colored-by-level2-imports { color: red; }
@@ -0,0 +1 @@
1
+ @import url('imports.css');
@@ -0,0 +1 @@
1
+ <html><body>Hello World!</body><head></head></html>
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <title>Test Percy::Capybara</title>
4
+ <link href="../css/base.css" rel="stylesheet" type="text/css">
5
+ <link href="/css/simple-imports.css" rel="stylesheet" type="text/css">
6
+ <link href="/css/level0-imports.css" rel="stylesheet" type="text/css">
7
+ <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" type="text/css">
8
+ <style>
9
+ .colored-by-inline { color: red; }
10
+ </style>
11
+ <h1>Hello World!</h1>
12
+ <div class="colored-by-inline">This should be red</div>
13
+ <div class="colored-by-base">This should be red</div>
14
+ <div class="colored-by-imports">This should be red</div>
15
+ <div class="colored-by-level0-imports">This should be red</div>
16
+ <div class="colored-by-level1-imports">This should be red</div>
17
+ <div class="colored-by-level2-imports">This should be red</div>
18
+ </html>
@@ -0,0 +1,49 @@
1
+ RSpec.describe Percy::Capybara do
2
+ before(:each) { Percy::Capybara.reset }
3
+
4
+ describe '#capybara_client' do
5
+ it 'returns the current client or creates a new one' do
6
+ capybara_client = Percy::Capybara.capybara_client
7
+ expect(capybara_client).to be
8
+ # Verify that it memoizes the current object by calling it again:
9
+ expect(Percy::Capybara.capybara_client).to eq(capybara_client)
10
+ end
11
+ end
12
+ describe '#snapshot' do
13
+ it 'passes all arguments through to the current capybara_client' do
14
+ mock_page = double('page')
15
+ capybara_client = Percy::Capybara.capybara_client
16
+ expect(capybara_client).to receive(:snapshot).with(mock_page, {}).once
17
+ Percy::Capybara.snapshot(mock_page)
18
+ expect(capybara_client).to receive(:snapshot).with(mock_page, name: '/foo.html (modal)').once
19
+ Percy::Capybara.snapshot(mock_page, name: '/foo.html (modal)')
20
+ end
21
+ end
22
+ describe '#snapshot' do
23
+ it 'delgates to Percy::Capybara::Client' do
24
+ capybara_client = Percy::Capybara.capybara_client
25
+ expect(capybara_client).to receive(:initialize_build).once
26
+ Percy::Capybara.initialize_build
27
+ end
28
+ end
29
+ describe '#finalize_build' do
30
+ it 'returns silently if no build is initialized' do
31
+ expect { Percy::Capybara.finalize_build }.to_not raise_error
32
+ end
33
+ it 'delgates to Percy::Capybara::Client' do
34
+ capybara_client = Percy::Capybara.capybara_client
35
+ build_data = {'data' => {'id' => 123}}
36
+ expect(capybara_client.client).to receive(:create_build).and_return(build_data).once
37
+ Percy::Capybara.initialize_build
38
+ expect(capybara_client).to receive(:finalize_current_build).once
39
+ Percy::Capybara.finalize_build
40
+ end
41
+ end
42
+ describe '#reset' do
43
+ it 'clears the current capybara_client' do
44
+ capybara_client = Percy::Capybara.capybara_client
45
+ Percy::Capybara.reset
46
+ expect(Percy::Capybara.capybara_client).to_not eq(capybara_client)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ require 'capybara/rspec'
2
+ require 'capybara/webkit'
3
+ require 'webmock/rspec'
4
+ require 'support/test_helpers'
5
+ require 'percy'
6
+ require 'percy/capybara'
7
+
8
+ RSpec.configure do |config|
9
+ config.include TestHelpers
10
+
11
+ config.expect_with :rspec do |expectations|
12
+ # This option will default to `true` in RSpec 4.
13
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
14
+ end
15
+
16
+ config.mock_with :rspec do |mocks|
17
+ mocks.verify_partial_doubles = true
18
+ end
19
+
20
+ config.disable_monkey_patching!
21
+ # config.warnings = true
22
+
23
+ # Run specs in random order to surface order dependencies. If you find an
24
+ # order dependency and want to debug it, you can fix the order by providing
25
+ # the seed, which is printed after each run.
26
+ # --seed 1234
27
+ config.order = :random
28
+
29
+ # Seed global randomization in this process using the `--seed` CLI option.
30
+ # Setting this allows you to use `--seed` to deterministically reproduce
31
+ # test failures related to randomization by passing the same `--seed` value
32
+ # as the one that triggered the failure.
33
+ Kernel.srand config.seed
34
+
35
+ # Comment this out to test the default Selenium/Firefox flow:
36
+ Capybara.javascript_driver = :webkit
37
+
38
+ config.before(:all) do
39
+ WebMock.disable_net_connect!(allow_localhost: true, allow: [/maxcdn.bootstrapcdn.com/])
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+
4
+ module TestHelpers
5
+ class ServerDown < Exception; end
6
+
7
+ def get_random_open_port
8
+ # Using a port of "0" relies on the system to pick an open port.
9
+ server = TCPServer.new('127.0.0.1', 0)
10
+ port = server.addr[1]
11
+ server.close
12
+ port
13
+ end
14
+
15
+ def verify_server_up(host)
16
+ http = HTTPClient.new
17
+ 4.times do
18
+ begin
19
+ http.get(host)
20
+ return true
21
+ rescue Errno::ECONNREFUSED
22
+ sleep 0.5
23
+ end
24
+ end
25
+ raise ServerDown, "Server failed to start: #{host}"
26
+ end
27
+ end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: percy-capybara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - Mike Fotinakis
7
+ - Perceptual Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-05 00:00:00.000000000 Z
11
+ date: 2015-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: percy-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -38,21 +66,111 @@ dependencies:
38
66
  - - "~>"
39
67
  - !ruby/object:Gem::Version
40
68
  version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: capybara
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: capybara-webkit
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: selenium-webdriver
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: webmock
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1'
41
139
  description: ''
42
140
  email:
43
- - mike@fotinakis.com
141
+ - team@percy.io
44
142
  executables: []
45
143
  extensions: []
46
144
  extra_rdoc_files: []
47
145
  files:
48
146
  - ".gitignore"
147
+ - ".rspec"
148
+ - ".travis.yml"
149
+ - ".yardopts"
49
150
  - Gemfile
151
+ - Guardfile
50
152
  - LICENSE
51
153
  - README.md
52
154
  - Rakefile
53
- - lib/perceptual.rb
54
- - lib/perceptual/version.rb
55
- - perceptual.gemspec
155
+ - lib/percy/capybara.rb
156
+ - lib/percy/capybara/client.rb
157
+ - lib/percy/capybara/client/builds.rb
158
+ - lib/percy/capybara/client/snapshots.rb
159
+ - lib/percy/capybara/version.rb
160
+ - percy-capybara.gemspec
161
+ - spec/lib/percy/capybara/client/builds_spec.rb
162
+ - spec/lib/percy/capybara/client/snapshots_spec.rb
163
+ - spec/lib/percy/capybara/client/testdata/css/base.css
164
+ - spec/lib/percy/capybara/client/testdata/css/imports.css
165
+ - spec/lib/percy/capybara/client/testdata/css/level0-imports.css
166
+ - spec/lib/percy/capybara/client/testdata/css/level1-imports.css
167
+ - spec/lib/percy/capybara/client/testdata/css/level2-imports.css
168
+ - spec/lib/percy/capybara/client/testdata/css/simple-imports.css
169
+ - spec/lib/percy/capybara/client/testdata/index.html
170
+ - spec/lib/percy/capybara/client/testdata/test-css.html
171
+ - spec/lib/percy/capybara_spec.rb
172
+ - spec/spec_helper.rb
173
+ - spec/support/test_helpers.rb
56
174
  homepage: ''
57
175
  licenses:
58
176
  - MIT
@@ -76,5 +194,19 @@ rubyforge_project:
76
194
  rubygems_version: 2.2.2
77
195
  signing_key:
78
196
  specification_version: 4
79
- summary: Write me.
80
- test_files: []
197
+ summary: Percy::Capybara
198
+ test_files:
199
+ - spec/lib/percy/capybara/client/builds_spec.rb
200
+ - spec/lib/percy/capybara/client/snapshots_spec.rb
201
+ - spec/lib/percy/capybara/client/testdata/css/base.css
202
+ - spec/lib/percy/capybara/client/testdata/css/imports.css
203
+ - spec/lib/percy/capybara/client/testdata/css/level0-imports.css
204
+ - spec/lib/percy/capybara/client/testdata/css/level1-imports.css
205
+ - spec/lib/percy/capybara/client/testdata/css/level2-imports.css
206
+ - spec/lib/percy/capybara/client/testdata/css/simple-imports.css
207
+ - spec/lib/percy/capybara/client/testdata/index.html
208
+ - spec/lib/percy/capybara/client/testdata/test-css.html
209
+ - spec/lib/percy/capybara_spec.rb
210
+ - spec/spec_helper.rb
211
+ - spec/support/test_helpers.rb
212
+ has_rdoc:
data/lib/perceptual.rb DELETED
@@ -1,5 +0,0 @@
1
- require "perceptual/version"
2
-
3
- module Perceptual
4
- # Your code goes here...
5
- end
@@ -1,3 +0,0 @@
1
- module Perceptual
2
- VERSION = "0.0.1"
3
- end
data/perceptual.gemspec DELETED
@@ -1,23 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'perceptual/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "percy-capybara"
8
- spec.version = Perceptual::VERSION
9
- spec.authors = ["Mike Fotinakis"]
10
- spec.email = ["mike@fotinakis.com"]
11
- spec.summary = "Write me."
12
- spec.description = ""
13
- spec.homepage = ""
14
- spec.license = "MIT"
15
-
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
-
21
- spec.add_development_dependency "bundler", "~> 1.7"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- end