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,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