eyes_selenium 2.15.0 → 2.16.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 (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