eyes_selenium 1.6.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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +82 -0
- data/Rakefile +1 -0
- data/eyes_selenium.gemspec +30 -0
- data/lib/eyes_selenium.rb +42 -0
- data/lib/eyes_selenium/capybara.rb +21 -0
- data/lib/eyes_selenium/eyes/agent_connecter.rb +39 -0
- data/lib/eyes_selenium/eyes/batch_info.rb +14 -0
- data/lib/eyes_selenium/eyes/dimension.rb +15 -0
- data/lib/eyes_selenium/eyes/driver.rb +146 -0
- data/lib/eyes_selenium/eyes/element.rb +78 -0
- data/lib/eyes_selenium/eyes/environment.rb +14 -0
- data/lib/eyes_selenium/eyes/eyes.rb +182 -0
- data/lib/eyes_selenium/eyes/eyes_keyboard.rb +25 -0
- data/lib/eyes_selenium/eyes/eyes_mouse.rb +60 -0
- data/lib/eyes_selenium/eyes/failure_reports.rb +4 -0
- data/lib/eyes_selenium/eyes/match_level.rb +7 -0
- data/lib/eyes_selenium/eyes/match_window_data.rb +18 -0
- data/lib/eyes_selenium/eyes/match_window_task.rb +71 -0
- data/lib/eyes_selenium/eyes/mouse_trigger.rb +19 -0
- data/lib/eyes_selenium/eyes/region.rb +22 -0
- data/lib/eyes_selenium/eyes/screenshot_taker.rb +18 -0
- data/lib/eyes_selenium/eyes/session.rb +14 -0
- data/lib/eyes_selenium/eyes/start_info.rb +34 -0
- data/lib/eyes_selenium/eyes/target_app.rb +17 -0
- data/lib/eyes_selenium/eyes/test_results.rb +21 -0
- data/lib/eyes_selenium/eyes/text_trigger.rb +15 -0
- data/lib/eyes_selenium/eyes/viewport_size.rb +104 -0
- data/lib/eyes_selenium/eyes_logger.rb +18 -0
- data/lib/eyes_selenium/utils.rb +5 -0
- data/lib/eyes_selenium/utils/image_delta_compressor.rb +147 -0
- data/lib/eyes_selenium/version.rb +3 -0
- data/spec/capybara_spec.rb +34 -0
- data/spec/driver_spec.rb +10 -0
- data/spec/eyes_spec.rb +157 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/test_app.rb +11 -0
- data/test_script.rb +21 -0
- metadata +226 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2013 Applitools
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Applitools
|
2
|
+
|
3
|
+
Applitools Ruby SDK.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'eyes_selenium'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install eyes_selenium
|
18
|
+
|
19
|
+
## Standalone Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Applitools::Eyes.config[:apikey] = 'XXX'
|
23
|
+
|
24
|
+
eyes = Applitools::Eyes.new
|
25
|
+
eyes.test(app_name: 'my app', test_name: 'my test') do |eyes, driver|
|
26
|
+
driver.get 'http://www.mywebsite.com'
|
27
|
+
eyes.check_window('home page')
|
28
|
+
driver.find_element(:css, "li#pricing").click
|
29
|
+
eyes.check_window('pricing')
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
## Integrated within your Capybara tests:
|
34
|
+
initializer:
|
35
|
+
```ruby
|
36
|
+
Capybara.run_server = false
|
37
|
+
# This is your api key, make sure you use it in all your tests.
|
38
|
+
Applitools::Eyes.config[:apikey] = 'XXX'
|
39
|
+
let(:eyes) do
|
40
|
+
webdriver = page.driver.browser
|
41
|
+
eyes = Applitools::Eyes.new browser: webdriver
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
spec:
|
46
|
+
```ruby
|
47
|
+
require 'capybara/rspec'
|
48
|
+
require 'eyes_selenium_ruby'
|
49
|
+
|
50
|
+
|
51
|
+
describe 'Applitools', :type=>:feature, :js=>true do
|
52
|
+
|
53
|
+
describe 'Test Applitools website' do
|
54
|
+
it 'should navigate from the main page to the features page' do
|
55
|
+
|
56
|
+
# Start visual testing with browser viewport set to 1024x768.
|
57
|
+
eyes.open(app_name: 'Applitools', test_name: 'Test Web Page', viewport_size: {width: 1024, height: 768})
|
58
|
+
|
59
|
+
visit 'http://www.applitools.com'
|
60
|
+
|
61
|
+
# Visual validation point #1
|
62
|
+
eyes.check_window('Main Page')
|
63
|
+
|
64
|
+
page.first('.read_more').click
|
65
|
+
|
66
|
+
# Visual validation point #2
|
67
|
+
eyes.check_window('Features Page')
|
68
|
+
|
69
|
+
eyes.close()
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
```
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
1. Fork it
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'eyes_selenium/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "eyes_selenium"
|
8
|
+
spec.version = Applitools::VERSION
|
9
|
+
spec.authors = ["Applitools team"]
|
10
|
+
spec.email = ["team@applitools.com"]
|
11
|
+
spec.description = "Applitools Ruby SDK"
|
12
|
+
spec.summary = "Applitools Ruby SDK"
|
13
|
+
spec.homepage = "http://www.applitools.com"
|
14
|
+
spec.license = "Apache License, Version 2.0"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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 "selenium-webdriver", [">= 2.37"]
|
22
|
+
spec.add_dependency "httparty"
|
23
|
+
spec.add_dependency "oily_png", [">= 1.1.0"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec", [">= 2.2.0"]
|
28
|
+
spec.add_development_dependency "capybara", [">= 2.1.0"]
|
29
|
+
spec.add_development_dependency "sinatra"
|
30
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'eyes_selenium/eyes_logger'
|
3
|
+
require 'json'
|
4
|
+
module Applitools
|
5
|
+
include EyesLogger
|
6
|
+
class EyesError < StandardError; end
|
7
|
+
class EyesAbort < EyesError; end
|
8
|
+
|
9
|
+
class TestFailedError < StandardError
|
10
|
+
attr_accessor :test_results
|
11
|
+
def initialize(message, test_results=nil)
|
12
|
+
super(message)
|
13
|
+
@test_results = test_results
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class NewTestError < TestFailedError; end
|
17
|
+
|
18
|
+
require 'eyes_selenium/utils'
|
19
|
+
require "eyes_selenium/eyes/agent_connecter"
|
20
|
+
require "eyes_selenium/eyes/target_app"
|
21
|
+
require 'eyes_selenium/eyes/batch_info'
|
22
|
+
require "eyes_selenium/eyes/dimension"
|
23
|
+
require "eyes_selenium/eyes/driver"
|
24
|
+
require 'eyes_selenium/eyes/element'
|
25
|
+
require 'eyes_selenium/eyes/environment'
|
26
|
+
require 'eyes_selenium/eyes/eyes'
|
27
|
+
require 'eyes_selenium/eyes/eyes_keyboard'
|
28
|
+
require 'eyes_selenium/eyes/eyes_mouse'
|
29
|
+
require 'eyes_selenium/eyes/failure_reports'
|
30
|
+
require 'eyes_selenium/eyes/match_level'
|
31
|
+
require 'eyes_selenium/eyes/match_window_data'
|
32
|
+
require 'eyes_selenium/eyes/match_window_task'
|
33
|
+
require 'eyes_selenium/eyes/mouse_trigger'
|
34
|
+
require 'eyes_selenium/eyes/region'
|
35
|
+
require 'eyes_selenium/eyes/screenshot_taker'
|
36
|
+
require 'eyes_selenium/eyes/session'
|
37
|
+
require 'eyes_selenium/eyes/start_info'
|
38
|
+
require 'eyes_selenium/eyes/test_results'
|
39
|
+
require 'eyes_selenium/eyes/text_trigger'
|
40
|
+
require "eyes_selenium/version"
|
41
|
+
require 'eyes_selenium/eyes/viewport_size'
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'eyes_selenium'
|
2
|
+
|
3
|
+
# Override create driver to inject into capybara's driver
|
4
|
+
class Applitools::Eyes
|
5
|
+
def test(params={}, &block)
|
6
|
+
begin
|
7
|
+
previous_driver = Capybara.current_driver
|
8
|
+
previous_browser = Capybara.current_session.driver.instance_variable_get(:@browser)
|
9
|
+
Capybara.current_driver = :selenium
|
10
|
+
Capybara.current_session.driver.instance_variable_set(:@browser, driver)
|
11
|
+
open(params)
|
12
|
+
yield(self, driver)
|
13
|
+
close
|
14
|
+
rescue Applitools::EyesError
|
15
|
+
ensure
|
16
|
+
abort_if_not_closed
|
17
|
+
Capybara.current_session.driver.instance_variable_set(:@browser, previous_browser)
|
18
|
+
Capybara.current_driver = previous_driver
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
class Applitools::AgentConnector
|
3
|
+
include HTTParty
|
4
|
+
headers 'Accept' => 'application/json'
|
5
|
+
#debug_output $stdout # comment out when not debugging
|
6
|
+
|
7
|
+
attr_reader :uri, :auth
|
8
|
+
def initialize(base_uri, username, password)
|
9
|
+
# Remove trailing slashes in base uri and add the running sessions endpoint uri.
|
10
|
+
@uri = base_uri.gsub(/\/$/,"") + "/api/sessions/running"
|
11
|
+
@auth = { username: username, password: password }
|
12
|
+
end
|
13
|
+
|
14
|
+
def match_window(session, data)
|
15
|
+
self.class.headers 'Content-Type' => 'application/octet-stream'
|
16
|
+
json_data = data.to_hash.to_json.encode('UTF-8') # Notice that this does not include the screenshot
|
17
|
+
body = [json_data.length].pack('L>') + json_data + data.screenshot
|
18
|
+
|
19
|
+
res = self.class.post(uri + "/#{session.id}",basic_auth: auth, body: body)
|
20
|
+
raise Applitools::EyesError.new("could not connect to server") if res.code != 200
|
21
|
+
res.parsed_response["asExpected"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_session(session_start_info)
|
25
|
+
self.class.headers 'Content-Type' => 'application/json'
|
26
|
+
res = self.class.post(uri, basic_auth: auth, body: { startInfo: session_start_info.to_hash }.to_json)
|
27
|
+
status_code = res.response.message
|
28
|
+
parsed_res = res.parsed_response
|
29
|
+
Applitools::Session.new(parsed_res["id"], parsed_res["url"], status_code == "Created" )
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop_session(session, aborted=nil)
|
33
|
+
self.class.headers 'Content-Type' => 'application/json'
|
34
|
+
res = self.class.delete(uri + "/#{session.id}", basic_auth: auth, params: { aborted: aborted.to_s })
|
35
|
+
parsed_res = res.parsed_response
|
36
|
+
parsed_res.delete("$id")
|
37
|
+
Applitools::TestResults.new(*parsed_res.values)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'selenium-webdriver'
|
3
|
+
class Applitools::Driver
|
4
|
+
|
5
|
+
include Selenium::WebDriver::DriverExtensions::HasInputDevices
|
6
|
+
|
7
|
+
attr_reader :remote_server_url, :remote_session_id, :user_agent, :screenshot_taker
|
8
|
+
attr_accessor :user_inputs, :browser
|
9
|
+
|
10
|
+
DEFAULT_DRIVER = :firefox
|
11
|
+
DRIVER_METHODS = [
|
12
|
+
:title, :execute_script, :execute_async_script, :quit, :close, :get,
|
13
|
+
:post, :page_source, :window_handles, :window_handle, :switch_to,
|
14
|
+
:navigate, :manage, :capabilities
|
15
|
+
]
|
16
|
+
|
17
|
+
## If driver is not provided, Applitools::Driver will default to Firefox driver
|
18
|
+
## Driver param can be a Selenium::WebDriver or a named symbol (:chrome)
|
19
|
+
#
|
20
|
+
## Example:
|
21
|
+
# eyes.open(browser: :chrome) ##=> will create chrome webdriver
|
22
|
+
# eyes.open(browser: Selenium::WebDriver.for(:chrome) ##=> will create the same thing
|
23
|
+
# eyes.open ##=> will create a webdriver according to Applitools::Driver::DEFAULT_DRIVER
|
24
|
+
def initialize(options={})
|
25
|
+
browser_obj = options.delete(:browser) || DEFAULT_DRIVER
|
26
|
+
@browser ||= case browser_obj
|
27
|
+
when Symbol
|
28
|
+
Selenium::WebDriver.for browser_obj
|
29
|
+
else
|
30
|
+
browser_obj
|
31
|
+
end
|
32
|
+
# TODO Remove this if all is good. We don't want to call 'quit' everytime the object is destroyed, we might still want to use the underlying driver
|
33
|
+
#at_exit { quit rescue nil }
|
34
|
+
|
35
|
+
@user_inputs = []
|
36
|
+
@remote_server_url = address_of_remote_server
|
37
|
+
@remote_session_id = remote_session_id
|
38
|
+
@user_agent = get_user_agent
|
39
|
+
begin
|
40
|
+
if browser.capabilities.takes_screenshot?
|
41
|
+
@screenshot_taker = false
|
42
|
+
else
|
43
|
+
@screenshot_taker = Applitools::ScreenshotTaker.new(@remote_server_url, @remote_session_id)
|
44
|
+
end
|
45
|
+
rescue => e
|
46
|
+
raise Applitools::EyesError.new "Can't take screenshots (#{e.message})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
DRIVER_METHODS.each do |method|
|
51
|
+
define_method method do |*args, &block|
|
52
|
+
browser.send(method,*args, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def screenshot_as(output_type)
|
57
|
+
return browser.screenshot_as(output_type) if !screenshot_taker
|
58
|
+
|
59
|
+
if output_type.downcase.to_sym != :base64
|
60
|
+
raise Applitools::EyesError.new("#{output_type} ouput type not supported for screenshot")
|
61
|
+
end
|
62
|
+
screenshot_taker.screenshot
|
63
|
+
end
|
64
|
+
|
65
|
+
def mouse
|
66
|
+
Applitools::EyesMouse.new(self, browser.mouse)
|
67
|
+
end
|
68
|
+
|
69
|
+
def keyboard
|
70
|
+
Applitools::EyesKeyboard.new(self, browser.keyboard)
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_element(by, selector)
|
74
|
+
Applitools::Element.new(self, browser.find_element(by, selector))
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_elements(by, selector)
|
78
|
+
browser.find_elements(by, selector).map { |el| Applitools::Element.new(self, el) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_application
|
82
|
+
Applitools::TargetApp.new(remote_server_url, remote_session_id, user_agent)
|
83
|
+
end
|
84
|
+
|
85
|
+
def clear_user_inputs
|
86
|
+
user_inputs.clear
|
87
|
+
end
|
88
|
+
|
89
|
+
def ie?
|
90
|
+
browser.to_s == 'ie'
|
91
|
+
end
|
92
|
+
|
93
|
+
def firefox?
|
94
|
+
browser.to_s == 'firefox'
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def address_of_remote_server
|
100
|
+
uri = URI(browser.current_url)
|
101
|
+
raise Applitools::EyesError.new("Failed to get remote web driver url") if uri.to_s.empty?
|
102
|
+
|
103
|
+
webdriver_host = uri.host
|
104
|
+
if ['127.0.0.1', 'localhost'].include?(webdriver_host) && !firefox? && !ie?
|
105
|
+
uri.host = get_local_ip || 'localhost'
|
106
|
+
end
|
107
|
+
|
108
|
+
uri
|
109
|
+
end
|
110
|
+
|
111
|
+
def remote_session_id
|
112
|
+
browser.remote_session_id
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_user_agent
|
116
|
+
execute_script "return navigator.userAgent"
|
117
|
+
rescue => e
|
118
|
+
puts "getUserAgent(): Failed to obtain user-agent string (#{e.message})"
|
119
|
+
end
|
120
|
+
|
121
|
+
def get_local_ip
|
122
|
+
begin
|
123
|
+
Socket.ip_address_list.detect do |intf|
|
124
|
+
intf.ipv4? and !intf.ipv4_loopback? and !intf.ipv4_multicast?
|
125
|
+
end.ip_address
|
126
|
+
rescue SocketError => e
|
127
|
+
raise Applitools::EyesError.new("Failed to get local IP! (#{e})")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
## .bridge, .session_id and .server_url are private methods in Selenium::WebDriver gem
|
134
|
+
module Selenium::WebDriver
|
135
|
+
class Driver
|
136
|
+
def remote_session_id
|
137
|
+
bridge.session_id
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class Remote::Http::Common
|
142
|
+
def get_server_url
|
143
|
+
server_url
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Applitools::Element
|
2
|
+
attr_accessor :driver, :web_element
|
3
|
+
|
4
|
+
ELEMENT_METHODS = [
|
5
|
+
:hash, :id, :id=, :bridge=, :submit, :clear, :tag_name, :attribute,
|
6
|
+
:selected?, :enabled?, :displayed?, :text, :css_value, :find_element,
|
7
|
+
:find_elements, :location, :size, :location_once_scrolled_into_view,
|
8
|
+
:ref, :to_json, :as_json
|
9
|
+
]
|
10
|
+
|
11
|
+
ELEMENT_METHODS.each do |method|
|
12
|
+
define_method method do |*args, &block|
|
13
|
+
web_element.send(method,*args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias_method :style, :css_value
|
17
|
+
alias_method :first, :find_element
|
18
|
+
alias_method :all, :find_elements
|
19
|
+
alias_method :[], :attribute
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
def initialize(driver, element)
|
24
|
+
@driver = driver
|
25
|
+
@web_element = element
|
26
|
+
end
|
27
|
+
|
28
|
+
def click
|
29
|
+
current_control = region
|
30
|
+
offset = current_control.middle_offset
|
31
|
+
driver.user_inputs << Applitools::MouseTrigger.new(:click, current_control, offset)
|
32
|
+
|
33
|
+
web_element.click
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
"EyesWebElement" + web_element.inspect
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
other.kind_of?(web_element.class) && web_element == other
|
42
|
+
end
|
43
|
+
alias_method :eql?, :==
|
44
|
+
|
45
|
+
def send_keys(*args)
|
46
|
+
current_control = region
|
47
|
+
Selenium::WebDriver::Keys.encode(args).each do |key|
|
48
|
+
driver.user_inputs << Applitools::TextTrigger.new(key.to_s, current_control)
|
49
|
+
end
|
50
|
+
|
51
|
+
web_element.send_keys(args)
|
52
|
+
end
|
53
|
+
alias_method :send_key, :send_keys
|
54
|
+
|
55
|
+
def region
|
56
|
+
point = location
|
57
|
+
left, top, width, height = point.x, point.y, 0, 0
|
58
|
+
|
59
|
+
begin
|
60
|
+
dimension = size
|
61
|
+
width, height = dimension.width, dimension.height
|
62
|
+
rescue
|
63
|
+
# Not supported on all platforms.
|
64
|
+
end
|
65
|
+
|
66
|
+
if left < 0
|
67
|
+
width = [0, width + left].max
|
68
|
+
left = 0
|
69
|
+
end
|
70
|
+
|
71
|
+
if top < 0
|
72
|
+
height = [0, height + top].max
|
73
|
+
top = 0
|
74
|
+
end
|
75
|
+
|
76
|
+
return Applitools::Region.new(left, top, width, height)
|
77
|
+
end
|
78
|
+
end
|