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,25 +0,0 @@
1
- class Applitools::EyesKeyboard
2
- attr_reader :keyboard, :driver
3
-
4
- def initialize(driver, keyboard)
5
- @driver = driver
6
- @keyboard = keyboard
7
- end
8
-
9
- def send_keys(*keys)
10
- active_element = Applitools::Element.new(driver, driver.switch_to.active_element)
11
- current_control = active_element.region
12
- Selenium::WebDriver::Keys.encode(keys).each do |key|
13
- driver.eyes.user_inputs << Applitools::TextTrigger.new(key.to_s, current_control)
14
- end
15
- keyboard.send_keys(*keys)
16
- end
17
-
18
- def press(key)
19
- keyboard.press(key)
20
- end
21
-
22
- def release(key)
23
- keyboard.release(key)
24
- end
25
- end
@@ -1,60 +0,0 @@
1
- class Applitools::EyesMouse
2
-
3
- attr_reader :driver, :mouse
4
- def initialize(driver, mouse)
5
- @driver = driver
6
- @mouse = mouse
7
- end
8
-
9
- def click(element = nil)
10
- extract_trigger_and_perform(:click, element)
11
- end
12
-
13
- def double_click(element = nil)
14
- extract_trigger_and_perform(:double_click, element)
15
- end
16
-
17
- def context_click(element = nil)
18
- extract_trigger_and_perform(:right_click, element)
19
- end
20
-
21
- def down(element = nil)
22
- extract_trigger_and_perform(:down, element)
23
- end
24
-
25
- def up(element = nil)
26
- extract_trigger_and_perform(:up, element)
27
- end
28
-
29
- def move_to(element, right_by = nil, down_by = nil)
30
- element = element.web_element if element.is_a?(Applitools::Element)
31
- location = element.location
32
- location.x = [0,location.x].max.round
33
- location.y = [0,location.y].max.round
34
- current_control = Applitools::Region.new(0,0, *location.values)
35
- driver.eyes.user_inputs << Applitools::MouseTrigger.new(:move, current_control, location)
36
- element = element.web_element if element.is_a?(Applitools::Element)
37
- mouse.move_to(element,right_by, down_by)
38
- end
39
-
40
- def move_by(right_by, down_by)
41
- right = [0,right_by].max.round
42
- down = [0,down_by].max.round
43
- location = Selenium::WebDriver::Point.new(right,down)
44
- current_control = Applitools::Region.new(0,0, right, down)
45
- driver.eyes.user_inputs << Applitools::MouseTrigger.new(:move, current_control, location)
46
- mouse.move_by(right_by,down_by)
47
- end
48
-
49
- private
50
-
51
- def extract_trigger_and_perform(method, element=nil, *args)
52
- location = element.location
53
- location.x = [0,location.x].max.round
54
- location.y = [0,location.y].max.round
55
- current_control = Applitools::Region.new(0,0, *location.values)
56
- driver.eyes.user_inputs << Applitools::MouseTrigger.new(method, current_control, location)
57
- element = element.web_element if element.is_a?(Applitools::Element)
58
- mouse.send(method,element,*args)
59
- end
60
- end
@@ -1,4 +0,0 @@
1
- module Applitools::FailureReports
2
- IMMEDIATE = 'Immediate'
3
- ON_CLOSE = 'OnClose'
4
- end
@@ -1,8 +0,0 @@
1
- module Applitools::MatchLevel
2
- NONE = 'None'
3
- LAYOUT = 'Layout'
4
- LAYOUT2 = 'Layout2'
5
- CONTENT = 'Content'
6
- STRICT = 'Strict'
7
- EXACT = 'Exact'
8
- end
@@ -1,18 +0,0 @@
1
- class Applitools::MatchWindowData
2
-
3
- attr_reader :user_inputs, :app_output, :tag, :ignore_mismatch, :screenshot
4
- def initialize(app_output,user_inputs=[], tag, ignore_mismatch, screenshot)
5
- @user_inputs = user_inputs
6
- @app_output = app_output
7
- @tag = tag
8
- @ignore_mismatch = ignore_mismatch
9
- @screenshot = screenshot
10
- end
11
-
12
- # IMPORTANT This method returns a hash WITHOUT the screenshot property. This is on purspose! The screenshot should
13
- # not be included as part of the json.
14
- def to_hash
15
- {userInputs: user_inputs.map(&:to_hash), appOutput: Hash[app_output.each_pair.to_a],
16
- tag: @tag, ignoreMismatch: @ignore_mismatch}
17
- end
18
- end
@@ -1,182 +0,0 @@
1
- require 'oily_png'
2
- require 'base64'
3
-
4
- class Applitools::MatchWindowTask
5
-
6
- private
7
- MATCH_INTERVAL = 0.5
8
- AppOutput = Struct.new(:title, :screenshot64)
9
-
10
- attr_reader :eyes, :agent_connector, :session, :driver, :default_retry_timeout, :last_checked_window ,:last_screenshot_bounds
11
-
12
- public
13
- #noinspection RubyParameterNamingConvention
14
- def initialize(eyes, agent_connector, session, driver, default_retry_timeout)
15
- @eyes = eyes
16
- @agent_connector = agent_connector
17
- @session = session
18
- @driver = driver
19
- @default_retry_timeout = default_retry_timeout
20
- @last_checked_window = nil # +ChunkyPNG::Canvas+
21
- @last_screenshot_bounds = Applitools::Region::EMPTY # +Applitools::Region+
22
- @current_screenshot = nil # +ChunkyPNG::Canvas+
23
- end
24
-
25
- def match_window(region, retry_timeout, tag, rotation, run_once_after_wait=false)
26
- if retry_timeout < 0
27
- retry_timeout = default_retry_timeout
28
- end
29
- EyesLogger.debug "Retry timeout set to: #{retry_timeout}"
30
- start = Time.now
31
- res = if retry_timeout.zero?
32
- run(region, tag, rotation)
33
- elsif run_once_after_wait
34
- run(region, tag, rotation, retry_timeout)
35
- else
36
- run_with_intervals(region, tag, rotation, retry_timeout)
37
- end
38
- elapsed_time = Time.now - start
39
- EyesLogger.debug "match_window(): Completed in #{format('%.2f', elapsed_time)} seconds"
40
- @last_checked_window = @current_screenshot
41
- @last_screenshot_bounds = region.empty? ? Applitools::Region.new(0, 0, last_checked_window.width, last_checked_window.height) : region
42
- #noinspection RubyUnnecessaryReturnStatement
43
- driver.eyes.clear_user_inputs and return res
44
- end
45
-
46
- def run(region, tag, rotation, wait_before_run=nil)
47
- EyesLogger.debug 'Trying matching once...'
48
- if wait_before_run
49
- EyesLogger.debug 'waiting before run...'
50
- sleep(wait_before_run)
51
- EyesLogger.debug 'waiting done!'
52
- end
53
- match(region, tag, rotation)
54
- end
55
-
56
- def run_with_intervals(region, tag, rotation, retry_timeout)
57
- # We intentionally take the first screenshot before starting the timer, to allow the page
58
- # just a tad more time to stabilize.
59
- EyesLogger.debug 'Matching with intervals...'
60
- data = prep_match_data(region, tag, rotation, true)
61
- start = Time.now
62
- as_expected = agent_connector.match_window(session, data)
63
- EyesLogger.debug "First call result: #{as_expected}"
64
- return true if as_expected
65
- EyesLogger.debug "Not as expected, performing retry (total timeout #{retry_timeout})"
66
- match_retry = Time.now - start
67
- while match_retry < retry_timeout
68
- EyesLogger.debug 'Waiting before match...'
69
- sleep(MATCH_INTERVAL)
70
- EyesLogger.debug 'Done! Matching...'
71
- return true if match(region, tag, rotation, true)
72
- match_retry = Time.now - start
73
- EyesLogger.debug "Elapsed time: #{match_retry}"
74
- end
75
- ## lets try one more time if we still don't have a match
76
- EyesLogger.debug 'Last attempt to match...'
77
- as_expected = match(region, tag, rotation)
78
- EyesLogger.debug "Match result: #{as_expected}"
79
- as_expected
80
- end
81
-
82
- private
83
-
84
- def get_clipped_region(region, image)
85
- left, top = [region.left, 0].max, [region.top, 0].max
86
- max_width = image.width - left
87
- max_height = image.height - top
88
- width, height = [region.width, max_width].min, [region.height, max_height].min
89
- Applitools::Region.new(left, top, width, height)
90
- end
91
-
92
- def prep_match_data(region, tag, rotation, ignore_mismatch)
93
- EyesLogger.debug 'Preparing match data...'
94
- title = eyes.title
95
- EyesLogger.debug 'Getting screenshot...'
96
- current_screenshot_encoded = driver.screenshot_as(:png, rotation)
97
- EyesLogger.debug 'Done! Creating image object from PNG...'
98
- @current_screenshot = ChunkyPNG::Image.from_blob(current_screenshot_encoded)
99
- EyesLogger.debug 'Done!'
100
- # If a region was defined, we refer to the sub-image defined by the region.
101
- unless region.empty?
102
- EyesLogger.debug 'Calculating clipped region...'
103
- # If the region is out of bounds, clip it
104
- clipped_region = get_clipped_region(region, @current_screenshot)
105
- raise Applitools::EyesError.new("Region is outside the viewport: #{region}") if clipped_region.empty?
106
- EyesLogger.debug 'Done! Cropping region...'
107
- @current_screenshot.crop!(clipped_region.left, clipped_region.top, clipped_region.width, clipped_region.height)
108
- EyesLogger.debug 'Done! Creating cropped image object...'
109
- current_screenshot_encoded = @current_screenshot.to_blob.force_encoding('BINARY')
110
- EyesLogger.debug 'Done!'
111
- end
112
- EyesLogger.debug 'Compressing screenshot...'
113
- compressed_screenshot = Applitools::Utils::ImageDeltaCompressor.compress_by_raw_blocks(@current_screenshot,
114
- current_screenshot_encoded,
115
- last_checked_window)
116
- EyesLogger.debug 'Done! Creating AppOuptut...'
117
- app_output = AppOutput.new(title, nil)
118
- user_inputs = []
119
- EyesLogger.debug 'Handling user inputs...'
120
- if !last_checked_window.nil?
121
- driver.eyes.user_inputs.each do |trigger|
122
- EyesLogger.debug 'Handling trigger...'
123
- if trigger.is_a?(Applitools::MouseTrigger)
124
- updated_trigger = nil
125
- trigger_left = trigger.control.left + trigger.location.x
126
- trigger_top = trigger.control.top + trigger.location.y
127
- if last_screenshot_bounds.contains?(trigger_left, trigger_top)
128
- trigger.control.intersect(last_screenshot_bounds)
129
- if trigger.control.empty?
130
- trigger_left = trigger_left - last_screenshot_bounds.left
131
- trigger_top = trigger_top -last_screenshot_bounds.top
132
- updated_trigger = Applitools::MouseTrigger.new(trigger.mouse_action, trigger.control, Selenium::WebDriver::Point.new(trigger_left, trigger_top))
133
- else
134
- trigger_left = trigger_left - trigger.control.left
135
- trigger_top = trigger_top - trigger.control.top
136
- control_left = trigger.control.left - last_screenshot_bounds.left
137
- control_top = trigger.control.top - last_screenshot_bounds.top
138
- updated_control = Applitools::Region.new(control_left, control_top, trigger.control.width, trigger.control.height)
139
- updated_trigger = Applitools::MouseTrigger.new(trigger.mouse_action, updated_control, Selenium::WebDriver::Point.new(trigger_left, trigger_top))
140
- end
141
- EyesLogger.debug 'Done with trigger!'
142
- user_inputs << updated_trigger
143
- else
144
- EyesLogger.info "Trigger ignored: #{trigger} (out of bounds)"
145
- end
146
- elsif trigger.is_a?(Applitools::TextTrigger)
147
- unless trigger.control.empty?
148
- trigger.control.intersect(last_screenshot_bounds)
149
- unless trigger.control.empty?
150
- control_left = trigger.control.left - last_screenshot_bounds.left
151
- control_top = trigger.control.top - last_screenshot_bounds.top
152
- updated_control = Applitools::Region.new(control_left, control_top, trigger.control.width, trigger.control.height)
153
- updated_trigger = Applitools::TextTrigger.new(trigger.text, updated_control)
154
- EyesLogger.debug 'Done with trigger!'
155
- user_inputs << updated_trigger
156
- else
157
- EyesLogger.info "Trigger ignored: #{trigger} (control out of bounds)"
158
- end
159
- else
160
- EyesLogger.info "Trigger ignored: #{trigger} (out of bounds)"
161
- end
162
- else
163
- EyesLogger.info "Trigger ignored: #{trigger} (Unrecognized trigger)"
164
- end
165
- end
166
- else
167
- EyesLogger.info 'Triggers ignored: no previous window checked'
168
- end
169
- EyesLogger.debug 'Creating MatchWindowData object..'
170
- match_window_data_obj = Applitools::MatchWindowData.new(app_output, user_inputs, tag, ignore_mismatch, compressed_screenshot)
171
- EyesLogger.debug 'Done creating MatchWindowData object!'
172
- match_window_data_obj
173
- end
174
-
175
- def match(region, tag, rotation, ignore_mismatch=false)
176
- EyesLogger.debug 'Match called...'
177
- data = prep_match_data(region, tag, rotation, ignore_mismatch)
178
- match_result = agent_connector.match_window(session, data)
179
- EyesLogger.debug 'Match done!'
180
- match_result
181
- end
182
- end
@@ -1,23 +0,0 @@
1
- class Applitools::MouseTrigger
2
-
3
- MOUSE_ACTION = { click: 'Click', right_click: 'RightClick', double_click: 'DoubleClick', move: 'Move', down: 'Down', up: 'Up' }
4
-
5
- attr_reader :mouse_action, :control, :location
6
-
7
- def initialize(mouse_action, control, location)
8
- @mouse_action = mouse_action
9
- @control = control
10
- @location = location
11
- end
12
-
13
- def to_hash
14
- {
15
- triggetType: 'Mouse', mouseAction: MOUSE_ACTION[mouse_action],
16
- control: control.to_hash, location: Hash[location.each_pair.to_a]
17
- }
18
- end
19
-
20
- def to_s
21
- "#{mouse_action} [#{control}] #{location.x}, #{location.y}"
22
- end
23
- end
@@ -1,72 +0,0 @@
1
- class Applitools::Region
2
- attr_accessor :left, :top, :width, :height
3
-
4
- def initialize(left, top, width, height)
5
- @left = left.round
6
- @top = top.round
7
- @width = width.round
8
- @height = height.round
9
- end
10
-
11
- EMPTY = Applitools::Region.new(0, 0, 0, 0)
12
-
13
- def make_empty
14
- @left = EMPTY.left
15
- @top = EMPTY.top
16
- @width = EMPTY.width
17
- @height = EMPTY.height
18
- end
19
-
20
- def empty?
21
- self.left == EMPTY.left && self.top == EMPTY.top && self.width == EMPTY.width && self.height == EMPTY.height
22
- end
23
-
24
- def right
25
- left + width
26
- end
27
-
28
- def bottom
29
- top + height
30
- end
31
-
32
- def intersecting?(other)
33
- ((left <= other.left && other.left <= right) || (other.left <= left && left <= other.right)) \
34
- && ((top <= other.top && other.top <= bottom) || (other.top <= top && top <=other.bottom))
35
- end
36
-
37
- def intersect(other)
38
- if !intersecting?(other)
39
- make_empty and return
40
- end
41
- i_left = (left >= other.left) ? left : other.left
42
- i_right = (right <= other.right) ? right : other.right
43
- i_top = (top >= other.top) ? top : other.top
44
- i_bottom = (bottom <= other.bottom) ? bottom : other.bottom
45
-
46
- self.left = i_left
47
- self.top = i_top
48
- self.width = i_right - i_left
49
- self.height = i_bottom - i_top
50
- end
51
-
52
- def contains?(other_left, other_top)
53
- other_left >= left && other_left <= right && \
54
- other_top >= top && other_top <= bottom
55
- end
56
-
57
- def middle_offset
58
- mid_x = width / 2
59
- mid_y = height / 2
60
- Selenium::WebDriver::Point.new(mid_x.round, mid_y.round)
61
- end
62
-
63
- def to_hash
64
- {
65
- left: left, top: top, height: height, width: width
66
- }
67
- end
68
-
69
- def to_s
70
- "(#{left}, #{top}), #{width} x #{height}"
71
- end
72
- end
@@ -1,18 +0,0 @@
1
- require 'httparty'
2
- class Applitools::ScreenshotTaker
3
- include HTTParty
4
- headers 'Accept' => 'application/json'
5
- headers 'Content-Type' => 'application/json'
6
-
7
- attr_reader :driver_server_uri, :driver_session_id
8
-
9
- def initialize(driver_server_uri, driver_session_id)
10
- @driver_server_uri = driver_server_uri
11
- @driver_session_id = driver_session_id
12
- end
13
-
14
- def screenshot
15
- res = self.class.get(driver_server_uri.to_s.gsub(/\/$/,"") + "/session/#{driver_session_id}/screenshot")
16
- res.parsed_response['value']
17
- end
18
- end
@@ -1,14 +0,0 @@
1
- class Applitools::Session
2
- attr_reader :eyes, :id, :url
3
- attr_accessor :new_session
4
- def initialize(session_id, session_url, new_session)
5
- @new_session = new_session
6
- @id = session_id
7
- @url = session_url
8
- end
9
-
10
- def new_session?
11
- self.new_session
12
- end
13
- end
14
-
@@ -1,32 +0,0 @@
1
- class Applitools::StartInfo
2
-
3
- ATTRIBUTES = %w[ agent_id app_id_or_name scenario_id_or_name batch_info env_name
4
- environment match_level ver_id branch_name parent_branch_name ]
5
-
6
- ATTRIBUTES.each do |attr|
7
- attr_accessor attr
8
- end
9
-
10
- ## add a config file with this stuff, and use hash arg
11
- def initialize(agent_id, app_id_or_name, scenario_id_or_name, batch_info, env_name, environment, match_level, ver_id=nil,
12
- branch_name=nil, parent_branch_name=nil)
13
- @agent_id = agent_id
14
- @app_id_or_name = app_id_or_name
15
- @ver_id = ver_id
16
- @scenario_id_or_name = scenario_id_or_name
17
- @batch_info = batch_info
18
- @env_name = env_name
19
- @environment = environment
20
- @match_level = match_level
21
- @branch_name = branch_name
22
- @parent_branch_name = parent_branch_name
23
- end
24
-
25
- def to_hash
26
- {
27
- AgentId: agent_id, AppIdOrName: app_id_or_name, VerId: ver_id, ScenarioIdOrName: scenario_id_or_name,
28
- BatchInfo: batch_info.to_hash, EnvName: env_name, Environment: environment.to_hash, matchLevel: match_level, branchName: branch_name,
29
- parentBranchName: parent_branch_name
30
- }
31
- end
32
- end