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.
Files changed (42) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +13 -0
  5. data/README.md +82 -0
  6. data/Rakefile +1 -0
  7. data/eyes_selenium.gemspec +30 -0
  8. data/lib/eyes_selenium.rb +42 -0
  9. data/lib/eyes_selenium/capybara.rb +21 -0
  10. data/lib/eyes_selenium/eyes/agent_connecter.rb +39 -0
  11. data/lib/eyes_selenium/eyes/batch_info.rb +14 -0
  12. data/lib/eyes_selenium/eyes/dimension.rb +15 -0
  13. data/lib/eyes_selenium/eyes/driver.rb +146 -0
  14. data/lib/eyes_selenium/eyes/element.rb +78 -0
  15. data/lib/eyes_selenium/eyes/environment.rb +14 -0
  16. data/lib/eyes_selenium/eyes/eyes.rb +182 -0
  17. data/lib/eyes_selenium/eyes/eyes_keyboard.rb +25 -0
  18. data/lib/eyes_selenium/eyes/eyes_mouse.rb +60 -0
  19. data/lib/eyes_selenium/eyes/failure_reports.rb +4 -0
  20. data/lib/eyes_selenium/eyes/match_level.rb +7 -0
  21. data/lib/eyes_selenium/eyes/match_window_data.rb +18 -0
  22. data/lib/eyes_selenium/eyes/match_window_task.rb +71 -0
  23. data/lib/eyes_selenium/eyes/mouse_trigger.rb +19 -0
  24. data/lib/eyes_selenium/eyes/region.rb +22 -0
  25. data/lib/eyes_selenium/eyes/screenshot_taker.rb +18 -0
  26. data/lib/eyes_selenium/eyes/session.rb +14 -0
  27. data/lib/eyes_selenium/eyes/start_info.rb +34 -0
  28. data/lib/eyes_selenium/eyes/target_app.rb +17 -0
  29. data/lib/eyes_selenium/eyes/test_results.rb +21 -0
  30. data/lib/eyes_selenium/eyes/text_trigger.rb +15 -0
  31. data/lib/eyes_selenium/eyes/viewport_size.rb +104 -0
  32. data/lib/eyes_selenium/eyes_logger.rb +18 -0
  33. data/lib/eyes_selenium/utils.rb +5 -0
  34. data/lib/eyes_selenium/utils/image_delta_compressor.rb +147 -0
  35. data/lib/eyes_selenium/version.rb +3 -0
  36. data/spec/capybara_spec.rb +34 -0
  37. data/spec/driver_spec.rb +10 -0
  38. data/spec/eyes_spec.rb +157 -0
  39. data/spec/spec_helper.rb +29 -0
  40. data/spec/test_app.rb +11 -0
  41. data/test_script.rb +21 -0
  42. metadata +226 -0
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in applitools.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -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,14 @@
1
+ class Applitools::BatchInfo
2
+ attr_reader :name, :started_at
3
+ def initialize(name=nil, started_at = Time.now)
4
+ @name = name
5
+ @started_at = started_at
6
+ end
7
+
8
+ def to_hash
9
+ {
10
+ name: name,
11
+ startedAt: started_at.iso8601
12
+ }
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ class Applitools::Dimension
2
+
3
+ attr_accessor :width, :height
4
+ def initialize(width, height)
5
+ @width = width
6
+ @height = height
7
+ end
8
+
9
+ def to_hash
10
+ { width: width, height: height}
11
+ end
12
+ def values
13
+ [width, height]
14
+ end
15
+ 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