eyes_selenium 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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