eyes_selenium 2.15.0 → 2.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.travis.yml +16 -0
  4. data/Gemfile +1 -1
  5. data/README.md +14 -4
  6. data/Rakefile +8 -1
  7. data/eyes_selenium.gemspec +26 -17
  8. data/lib/applitools/base/batch_info.rb +19 -0
  9. data/lib/applitools/base/dimension.rb +21 -0
  10. data/lib/applitools/base/environment.rb +19 -0
  11. data/lib/applitools/base/mouse_trigger.rb +33 -0
  12. data/lib/applitools/base/point.rb +21 -0
  13. data/lib/applitools/base/region.rb +77 -0
  14. data/lib/applitools/base/server_connector.rb +113 -0
  15. data/lib/applitools/base/session.rb +15 -0
  16. data/lib/applitools/base/start_info.rb +34 -0
  17. data/lib/applitools/base/test_results.rb +36 -0
  18. data/lib/applitools/base/text_trigger.rb +22 -0
  19. data/lib/applitools/eyes.rb +393 -0
  20. data/lib/applitools/eyes_logger.rb +40 -0
  21. data/lib/applitools/method_tracer.rb +22 -0
  22. data/lib/applitools/selenium/driver.rb +194 -0
  23. data/lib/applitools/selenium/element.rb +66 -0
  24. data/lib/applitools/selenium/keyboard.rb +27 -0
  25. data/lib/applitools/selenium/match_window_data.rb +24 -0
  26. data/lib/applitools/selenium/match_window_task.rb +190 -0
  27. data/lib/applitools/selenium/mouse.rb +62 -0
  28. data/lib/applitools/selenium/viewport_size.rb +128 -0
  29. data/lib/applitools/utils/image_delta_compressor.rb +150 -0
  30. data/lib/applitools/utils/image_utils.rb +63 -0
  31. data/lib/applitools/utils/utils.rb +52 -0
  32. data/lib/applitools/version.rb +3 -0
  33. data/lib/eyes_selenium.rb +9 -29
  34. data/spec/driver_passthrough_spec.rb +25 -25
  35. data/spec/spec_helper.rb +5 -8
  36. data/test/appium_example_script.rb +57 -0
  37. data/{test_script.rb → test/test_script.rb} +7 -9
  38. data/{watir_test.rb → test/watir_test_script.rb} +6 -4
  39. metadata +120 -48
  40. data/appium_eyes_example.rb +0 -56
  41. data/lib/eyes_selenium/capybara.rb +0 -21
  42. data/lib/eyes_selenium/eyes/agent_connector.rb +0 -76
  43. data/lib/eyes_selenium/eyes/batch_info.rb +0 -19
  44. data/lib/eyes_selenium/eyes/dimension.rb +0 -15
  45. data/lib/eyes_selenium/eyes/driver.rb +0 -266
  46. data/lib/eyes_selenium/eyes/element.rb +0 -78
  47. data/lib/eyes_selenium/eyes/environment.rb +0 -15
  48. data/lib/eyes_selenium/eyes/eyes.rb +0 -396
  49. data/lib/eyes_selenium/eyes/eyes_keyboard.rb +0 -25
  50. data/lib/eyes_selenium/eyes/eyes_mouse.rb +0 -60
  51. data/lib/eyes_selenium/eyes/failure_reports.rb +0 -4
  52. data/lib/eyes_selenium/eyes/match_level.rb +0 -8
  53. data/lib/eyes_selenium/eyes/match_window_data.rb +0 -18
  54. data/lib/eyes_selenium/eyes/match_window_task.rb +0 -182
  55. data/lib/eyes_selenium/eyes/mouse_trigger.rb +0 -23
  56. data/lib/eyes_selenium/eyes/region.rb +0 -72
  57. data/lib/eyes_selenium/eyes/screenshot_taker.rb +0 -18
  58. data/lib/eyes_selenium/eyes/session.rb +0 -14
  59. data/lib/eyes_selenium/eyes/start_info.rb +0 -32
  60. data/lib/eyes_selenium/eyes/test_results.rb +0 -32
  61. data/lib/eyes_selenium/eyes/text_trigger.rb +0 -19
  62. data/lib/eyes_selenium/eyes/viewport_size.rb +0 -105
  63. data/lib/eyes_selenium/eyes_logger.rb +0 -47
  64. data/lib/eyes_selenium/utils.rb +0 -6
  65. data/lib/eyes_selenium/utils/image_delta_compressor.rb +0 -149
  66. data/lib/eyes_selenium/utils/image_utils.rb +0 -76
  67. data/lib/eyes_selenium/version.rb +0 -3
@@ -1,56 +0,0 @@
1
- require 'eyes_selenium'
2
- require 'appium_lib'
3
-
4
- ##
5
- # Based on Appium example: https://github.com/appium/appium/blob/master/sample-code/examples/ruby/
6
-
7
-
8
- def android_caps
9
- {
10
- deviceName: 'Samsung Galaxy S4 Emulator',
11
- platformName: 'Android',
12
- platformVersion: 4.4,
13
- app: ENV['ANDROID_NOTES_LIST_APP'],
14
- appPackage: 'com.example.android.notepad',
15
- appActivity: '.NotesList',
16
- # orientation: 'landscape',
17
- newCommandTimeout: 300
18
- }
19
- end
20
- def ios_caps
21
- {
22
- deviceName: 'iPhone 6',
23
- platformName: 'ios',
24
- platformVersion: 8.3,
25
- app: ENV['IOS_DEMO_APP'],
26
- orientation: 'landscape',
27
- newCommandTimeout: 300
28
- }
29
- end
30
-
31
- def appium_opts
32
- {
33
- server_url: 'http://127.0.0.1:4723/wd/hub',
34
- }
35
- end
36
-
37
-
38
- @eyes = Applitools::Eyes.new
39
- @eyes.log_handler = Logger.new(STDOUT)
40
- @eyes.api_key = ENV['APPLITOOLS_API_KEY']
41
- begin
42
- # @driver = Appium::Driver.new({caps: android_caps, appium_lib: appium_opts})
43
- @driver = Appium::Driver.new({caps: ios_caps, appium_lib: appium_opts})
44
- @driver.start_driver
45
- # @driver.driver.rotate :landscape
46
- puts "Screen size: #{@driver.driver.manage.window.size}"
47
- puts "orientation: #{@driver.driver.orientation}"
48
- puts @driver.caps
49
- @eyes.open(app_name: 'Selenium Israel', test_name: 'Appium Notepad', driver: @driver)
50
- @eyes.check_window("No notes")
51
-
52
- @eyes.close
53
- ensure
54
- @eyes.abort_if_not_closed
55
- @driver.driver_quit
56
- end
@@ -1,21 +0,0 @@
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
@@ -1,76 +0,0 @@
1
- require 'httparty'
2
- class Applitools::AgentConnector
3
- include HTTParty
4
- headers 'Accept' => 'application/json'
5
- ssl_ca_file File.join(File.dirname(File.expand_path(__FILE__)), '../../../certs/cacert.pem').to_s
6
- default_timeout 300
7
-
8
- # comment out when not debugging
9
- #http_proxy 'localhost', 8888
10
- #debug_output $stdout
11
-
12
- attr_accessor :server_url, :api_key
13
-
14
- def server_url=(server_url)
15
- @server_url = server_url
16
- # Remove trailing slashes in server url and add the running sessions endpoint uri.
17
- @endpoint_uri = server_url.gsub(/\/$/,'') + '/api/sessions/running'
18
- end
19
-
20
- def initialize(server_url)
21
- self.server_url = server_url
22
- end
23
-
24
- def match_window(session, data)
25
- self.class.headers 'Content-Type' => 'application/octet-stream'
26
- json_data = data.to_hash.to_json.force_encoding('BINARY') # Notice that this does not include the screenshot
27
- body = [json_data.length].pack('L>') + json_data + data.screenshot
28
- EyesLogger.debug 'Sending match data...'
29
- res = self.class.post(@endpoint_uri + "/#{session.id}", query: {apiKey: api_key}, body: body)
30
- raise Applitools::EyesError.new('could not connect to server') if res.code != 200
31
- EyesLogger.debug "Got response! #{res.parsed_response['asExpected']}"
32
- res.parsed_response['asExpected']
33
- end
34
-
35
- def start_session(session_start_info)
36
- self.class.headers 'Content-Type' => 'application/json'
37
- res = self.class.post(@endpoint_uri, query: {apiKey: api_key}, body: { startInfo: session_start_info.to_hash }.to_json)
38
- status_code = res.response.message
39
- parsed_res = res.parsed_response
40
- Applitools::Session.new(parsed_res['id'], parsed_res['url'], status_code == 'Created' )
41
- end
42
-
43
- def stop_session(session, aborted=nil, save=false)
44
- self.class.headers 'Content-Type' => 'application/json'
45
- res = self.class.send_long_request('stop_session') do
46
- self.class.delete(@endpoint_uri + "/#{session.id}", query: { aborted: aborted.to_s, updateBaseline: save.to_s, apiKey: api_key })
47
- end
48
- parsed_res = res.parsed_response
49
- parsed_res.delete('$id')
50
- Applitools::TestResults.new(*parsed_res.values)
51
- end
52
-
53
- private
54
- ##
55
- # Static method for sending long running requests
56
- # Args:
57
- # name: (String) name of the method being executed
58
- # request_block: (block) The actual block to be executed. Will be called using "yield"
59
- # noinspection RubyUnusedLocalVariable
60
- def self.send_long_request(name, &request_block)
61
- delay = 2 # seconds
62
-
63
- headers 'Eyes-Expect' => '202-accepted'
64
- while true
65
- # Date should be in RFC 1123 format
66
- headers 'Eyes-Date' => Time.now.utc.strftime('%a, %d %b %Y %H:%M:%S GMT')
67
- res = yield
68
- if res.code != 202
69
- return res
70
- end
71
- EyesLogger.debug "#{name}: Still running... Retrying in #{delay}s"
72
- sleep delay
73
- delay = [10, (delay*1.5).round].min
74
- end
75
- end
76
- end
@@ -1,19 +0,0 @@
1
- require 'securerandom'
2
-
3
- class Applitools::BatchInfo
4
- attr_accessor :id
5
- attr_reader :name, :started_at
6
- def initialize(name=nil, started_at = Time.now)
7
- @name = name
8
- @started_at = started_at
9
- @id = SecureRandom.uuid
10
- end
11
-
12
- def to_hash
13
- {
14
- name: name,
15
- id: id,
16
- startedAt: started_at.iso8601
17
- }
18
- end
19
- end
@@ -1,15 +0,0 @@
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
@@ -1,266 +0,0 @@
1
- require 'socket'
2
- require 'selenium-webdriver'
3
-
4
- class Applitools::Driver
5
-
6
- # Rotates the image as necessary. The rotation is either manually forced by passing a value in
7
- # the +rotation+ parameter, or automatically inferred if the +rotation+ parameter is +nil+.
8
- #
9
- # +driver+:: +Applitools::Driver+ The driver which produced the screenshot.
10
- # +image+:: +ChunkyPNG::Canvas+ The image to normalize.
11
- # +rotation+:: +Integer+|+nil+ The degrees by which to rotate the image: positive values = clockwise rotation,
12
- # negative values = counter-clockwise, 0 = force no rotation, +nil+ = rotate
13
- # automatically when needed.
14
- #
15
- def self.normalize_rotation!(driver, image, rotation)
16
- EyesLogger.debug "#{__method__}()"
17
- if rotation != 0
18
- num_quadrants = 0
19
- if !rotation.nil?
20
- if rotation % 90 != 0
21
- raise Applitools::EyesError.new(
22
- "Currently only quadrant rotations are supported. Current rotation: #{rotation}")
23
- end
24
- num_quadrants = (rotation / 90).to_i
25
- elsif rotation.nil? && driver.mobile_device? && driver.landscape_orientation? && image.height > image.width
26
- # For Android, we need to rotate images to the right, and for iOS to the left.
27
- num_quadrants = driver.android? ? 1 : -1
28
- end
29
- Applitools::Utils::ImageUtils.quadrant_rotate!(image, num_quadrants)
30
- end
31
- end
32
-
33
-
34
- include Selenium::WebDriver::DriverExtensions::HasInputDevices
35
-
36
- attr_reader :remote_server_url, :remote_session_id, :screenshot_taker, :eyes
37
- attr_accessor :driver
38
-
39
- DRIVER_METHODS = [
40
- :title, :execute_script, :execute_async_script, :quit, :close, :get,
41
- :post, :page_source, :window_handles, :window_handle, :switch_to,
42
- :navigate, :manage, :capabilities, :current_url
43
- ]
44
-
45
- ## If driver is not provided, Applitools::Driver will raise an EyesError exception.
46
- #
47
- def initialize(eyes, options)
48
- @driver = options[:driver]
49
- @is_mobile_device = options.fetch(:is_mobile_device, false)
50
- @eyes = eyes
51
- # FIXME fix getting "remote address url" or remove "Screenshot taker" altogether.
52
- # @remote_server_url = address_of_remote_server
53
- @remote_server_url = 'MOCK_URL'
54
- @remote_session_id = remote_session_id
55
- begin
56
- #noinspection RubyResolve
57
- if driver.capabilities.takes_screenshot?
58
- @screenshot_taker = false
59
- else
60
- @screenshot_taker = Applitools::ScreenshotTaker.new(@remote_server_url, @remote_session_id)
61
- end
62
- rescue => e
63
- raise Applitools::EyesError.new "Can't take screenshots (#{e.message})"
64
- end
65
- end
66
-
67
- DRIVER_METHODS.each do |method|
68
- define_method method do |*args, &block|
69
- driver.send(method,*args, &block)
70
- end
71
- end
72
-
73
- # Returns:
74
- # +String+ The platform name or +nil+ if it is undefined.
75
- def platform_name
76
- driver.capabilities['platformName']
77
- end
78
-
79
- # Returns:
80
- # +String+ The platform version or +nil+ if it is undefined.
81
- def platform_version
82
- version = driver.capabilities['platformVersion']
83
- version.nil? ? nil : version.to_s
84
- end
85
-
86
- # Returns:
87
- # +true+ if the driver is an Android driver.
88
- def android?
89
- platform_name.to_s.upcase == 'ANDROID'
90
- end
91
-
92
- # Returns:
93
- # +true+ if the driver is an iOS driver.
94
- def ios?
95
- platform_name.to_s.upcase == 'IOS'
96
- end
97
-
98
- # Returns:
99
- # +true+ if the driver orientation is landscape.
100
- def landscape_orientation?
101
- begin
102
- driver.orientation.to_s.upcase == 'LANDSCAPE'
103
- rescue NameError
104
- EyesLogger.debug 'driver has no "orientation" attribute. Assuming Portrait.'
105
- end
106
- end
107
-
108
- # Returns:
109
- # +true+ if the platform running the test is a mobile platform. +false+ otherwise.
110
- def mobile_device?
111
- # We CAN'T check if the device is an +Appium::Driver+ since it is not a RemoteWebDriver. Because of that we use a
112
- # flag we got as an option in the constructor.
113
- @is_mobile_device
114
- end
115
-
116
- # Return a PNG screenshot in the given format as a string
117
- #
118
- # +output_type+:: +Symbol+ The format of the screenshot. Accepted values are +:base64+ and +:png+.
119
- # +rotation+:: +Integer+|+nil+ The degrees by which to rotate the image: positive values = clockwise rotation,
120
- # negative values = counter-clockwise, 0 = force no rotation, +nil+ = rotate
121
- # automatically when needed.
122
- #
123
- # Returns: +String+ A screenshot in the requested format.
124
- def screenshot_as(output_type, rotation=nil)
125
- # FIXME Check if screenshot_taker is still required
126
- screenshot = screenshot_taker ? screenshot_taker.screenshot : driver.screenshot_as(:base64)
127
- screenshot = Applitools::Utils::ImageUtils.png_image_from_base64(screenshot)
128
- Applitools::Driver.normalize_rotation!(self, screenshot, rotation)
129
- case output_type
130
- when :base64
131
- screenshot = Applitools::Utils::ImageUtils.base64_from_png_image(screenshot)
132
- when :png
133
- screenshot = Applitools::Utils::ImageUtils.bytes_from_png_image(screenshot)
134
- else
135
- raise Applitools::EyesError.new("Unsupported screenshot output type #{output_type.to_s}")
136
- end
137
- screenshot.force_encoding('BINARY')
138
- end
139
-
140
- def mouse
141
- Applitools::EyesMouse.new(self, driver.mouse)
142
- end
143
-
144
- def keyboard
145
- Applitools::EyesKeyboard.new(self, driver.keyboard)
146
- end
147
-
148
- FINDERS = {
149
- :class => 'class name',
150
- :class_name => 'class name',
151
- :css => 'css selector',
152
- :id => 'id',
153
- :link => 'link text',
154
- :link_text => 'link text',
155
- :name => 'name',
156
- :partial_link_text => 'partial link text',
157
- :tag_name => 'tag name',
158
- :xpath => 'xpath',
159
- }
160
-
161
- def find_element(*args)
162
- how, what = extract_args(args)
163
-
164
- # Make sure that "how" is a valid locator.
165
- unless FINDERS[how.to_sym]
166
- raise ArgumentError, "cannot find element by #{how.inspect}"
167
- end
168
-
169
- Applitools::Element.new(self, driver.find_element(how, what))
170
- end
171
-
172
- def find_elements(*args)
173
- how, what = extract_args(args)
174
-
175
- unless FINDERS[how.to_sym]
176
- raise ArgumentError, "cannot find element by #{how.inspect}"
177
- end
178
-
179
- driver.find_elements(how, what).map { |el| Applitools::Element.new(self, el) }
180
- end
181
-
182
- def ie?
183
- driver.to_s == 'ie'
184
- end
185
-
186
- def firefox?
187
- driver.to_s == 'firefox'
188
- end
189
-
190
- def user_agent
191
- execute_script 'return navigator.userAgent'
192
- rescue => e
193
- EyesLogger.info "getUserAgent(): Failed to obtain user-agent string (#{e.message})"
194
- return nil
195
- end
196
-
197
- private
198
-
199
- # FIXME Fix extraction of remote server, or remove completely if removed ScreenshotTaker
200
- # def address_of_remote_server
201
- # # The driver's url is not available using a standard interface, so we use reflection to get it.
202
- # #noinspection RubyResolve
203
- # uri = URI(driver.instance_eval{@bridge}.instance_eval{@http}.instance_eval{@server_url})
204
- # raise Applitools::EyesError.new('Failed to get remote web driver url') if uri.to_s.empty?
205
- #
206
- # webdriver_host = uri.host
207
- # if %w[127.0.0.1 localhost].include?(webdriver_host) && !firefox? && !ie?
208
- # uri.host = get_local_ip || 'localhost'
209
- # end
210
- #
211
- # uri
212
- # end
213
-
214
- def remote_session_id
215
- driver.remote_session_id
216
- end
217
-
218
- def get_local_ip
219
- begin
220
- Socket.ip_address_list.detect do |intf|
221
- intf.ipv4? and !intf.ipv4_loopback? and !intf.ipv4_multicast?
222
- end.ip_address
223
- rescue SocketError => e
224
- raise Applitools::EyesError.new("Failed to get local IP! (#{e})")
225
- end
226
- end
227
-
228
- def extract_args(args)
229
- case args.size
230
- when 2
231
- args
232
- when 1
233
- arg = args.first
234
-
235
- unless arg.respond_to?(:shift)
236
- raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift"
237
- end
238
-
239
- # this will be a single-entry hash, so use #shift over #first or #[]
240
- arr = arg.dup.shift
241
- unless arr.size == 2
242
- raise ArgumentError, "expected #{arr.inspect} to have 2 elements"
243
- end
244
-
245
- arr
246
- else
247
- raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
248
- end
249
- end
250
-
251
- end
252
-
253
- ## .bridge, .session_id and .server_url are private methods in Selenium::WebDriver gem
254
- module Selenium::WebDriver
255
- class Driver
256
- def remote_session_id
257
- bridge.session_id
258
- end
259
- end
260
-
261
- class Remote::Http::Common
262
- def get_server_url
263
- server_url
264
- end
265
- end
266
- end