eyes_selenium 2.39.1 → 3.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/applitools/capybara.rb +1 -1
  3. data/lib/applitools/selenium/border_aware_element_content_location_provider.rb +41 -0
  4. data/lib/applitools/selenium/browser.rb +1 -0
  5. data/lib/applitools/selenium/capybara/capybara_settings.rb +8 -0
  6. data/lib/applitools/selenium/capybara/driver.rb +1 -0
  7. data/lib/applitools/selenium/context_based_scale_provider.rb +36 -0
  8. data/lib/applitools/selenium/css_translate_position_provider.rb +66 -0
  9. data/lib/applitools/selenium/driver.rb +159 -44
  10. data/lib/applitools/selenium/element.rb +100 -8
  11. data/lib/applitools/selenium/element_position_provider.rb +48 -0
  12. data/lib/applitools/selenium/eyes.rb +922 -0
  13. data/lib/applitools/selenium/eyes_target_locator.rb +191 -0
  14. data/lib/applitools/selenium/eyes_web_driver_screenshot.rb +274 -0
  15. data/lib/applitools/selenium/frame.rb +24 -0
  16. data/lib/applitools/selenium/frame_chain.rb +68 -0
  17. data/lib/applitools/selenium/full_page_capture_algorithm.rb +162 -0
  18. data/lib/applitools/selenium/keyboard.rb +1 -0
  19. data/lib/applitools/selenium/match_window_task.rb +0 -197
  20. data/lib/applitools/selenium/mouse.rb +1 -0
  21. data/lib/applitools/selenium/move_to_region_visibility_strategy.rb +33 -0
  22. data/lib/applitools/selenium/nop_region_visibility_strategy.rb +16 -0
  23. data/lib/applitools/selenium/scroll_position_provider.rb +52 -0
  24. data/lib/applitools/selenium/takes_screenshot_image_provider.rb +36 -0
  25. data/lib/applitools/version.rb +1 -1
  26. data/lib/eyes_selenium.rb +19 -30
  27. metadata +24 -254
  28. data/.gitignore +0 -18
  29. data/.rspec +0 -2
  30. data/.rubocop.yml +0 -57
  31. data/.travis.yml +0 -17
  32. data/Gemfile +0 -4
  33. data/LICENSE.txt +0 -13
  34. data/README.md +0 -38
  35. data/Rakefile +0 -11
  36. data/certs/cacert.pem +0 -3557
  37. data/examples/appium_example_script.rb +0 -59
  38. data/examples/capybara_example.rb +0 -82
  39. data/examples/sauce_capybara_example.rb +0 -41
  40. data/examples/sauce_example.rb +0 -34
  41. data/examples/simple_test_script.rb +0 -23
  42. data/examples/watir_test_script.rb +0 -26
  43. data/eyes_selenium.gemspec +0 -89
  44. data/lib/applitools/appium_driver.rb +0 -7
  45. data/lib/applitools/base/batch_info.rb +0 -21
  46. data/lib/applitools/base/dimension.rb +0 -36
  47. data/lib/applitools/base/environment.rb +0 -19
  48. data/lib/applitools/base/image_position.rb +0 -10
  49. data/lib/applitools/base/mouse_trigger.rb +0 -33
  50. data/lib/applitools/base/point.rb +0 -34
  51. data/lib/applitools/base/region.rb +0 -112
  52. data/lib/applitools/base/server_connector.rb +0 -120
  53. data/lib/applitools/base/session.rb +0 -15
  54. data/lib/applitools/base/start_info.rb +0 -34
  55. data/lib/applitools/base/test_results.rb +0 -28
  56. data/lib/applitools/base/text_trigger.rb +0 -22
  57. data/lib/applitools/extensions.rb +0 -17
  58. data/lib/applitools/eyes.rb +0 -460
  59. data/lib/applitools/eyes_logger.rb +0 -38
  60. data/lib/applitools/method_tracer.rb +0 -22
  61. data/lib/applitools/poltergeist/applitools_compatible.rb +0 -28
  62. data/lib/applitools/poltergeist/driver.rb +0 -11
  63. data/lib/applitools/sauce.rb +0 -2
  64. data/lib/applitools/selenium/match_window_data.rb +0 -28
  65. data/lib/applitools/selenium_webdriver.rb +0 -8
  66. data/lib/applitools/utils/image_delta_compressor.rb +0 -148
  67. data/lib/applitools/utils/image_utils.rb +0 -143
  68. data/lib/applitools/utils/utils.rb +0 -49
  69. data/lib/applitools/watir_browser.rb +0 -8
  70. data/spec/driver_passthrough_spec.rb +0 -68
  71. data/spec/fixtures/static_test_file.html +0 -15
  72. data/spec/spec_helper.rb +0 -17
@@ -1,10 +0,0 @@
1
- module Applitools::Base
2
- class ImagePosition
3
- attr_accessor :image, :position
4
-
5
- def initialize(image, position)
6
- @image = image
7
- @position = position
8
- end
9
- end
10
- end
@@ -1,33 +0,0 @@
1
- module Applitools::Base
2
- class MouseTrigger
3
- MOUSE_ACTION = {
4
- click: 'Click',
5
- right_click: 'RightClick',
6
- double_click: 'DoubleClick',
7
- move: 'Move',
8
- down: 'Down',
9
- up: 'Up'
10
- }.freeze
11
-
12
- attr_reader :mouse_action, :control, :location
13
-
14
- def initialize(mouse_action, control, location)
15
- @mouse_action = mouse_action
16
- @control = control
17
- @location = location
18
- end
19
-
20
- def to_hash
21
- {
22
- trigget_type: 'Mouse',
23
- mouse_action: MOUSE_ACTION[mouse_action],
24
- control: control.to_hash,
25
- location: location.to_hash
26
- }
27
- end
28
-
29
- def to_s
30
- "#{mouse_action} [#{control}] #{location.x}, #{location.y}"
31
- end
32
- end
33
- end
@@ -1,34 +0,0 @@
1
- module Applitools::Base
2
- class Point
3
- attr_accessor :x, :y
4
-
5
- alias_attribute :left, :x
6
- alias_attribute :top, :y
7
-
8
- def initialize(x, y)
9
- @x = x
10
- @y = y
11
- end
12
-
13
- TOP_LEFT = Point.new(0, 0)
14
-
15
- def ==(other)
16
- return super.==(other) unless other.is_a?(Point)
17
- @x == other.x && @y == other.y
18
- end
19
-
20
- alias eql? ==
21
-
22
- def hash
23
- @x.hash & @y.hash
24
- end
25
-
26
- def to_hash(options = {})
27
- options[:region] ? { left: left, top: top } : { x: x, y: y }
28
- end
29
-
30
- def values
31
- [x, y]
32
- end
33
- end
34
- end
@@ -1,112 +0,0 @@
1
- module Applitools::Base
2
- class Region
3
- attr_accessor :left, :top, :width, :height
4
-
5
- def initialize(left, top, width, height)
6
- @left = left.round
7
- @top = top.round
8
- @width = width.round
9
- @height = height.round
10
- end
11
-
12
- EMPTY = Region.new(0, 0, 0, 0)
13
-
14
- def make_empty
15
- @left = EMPTY.left
16
- @top = EMPTY.top
17
- @width = EMPTY.width
18
- @height = EMPTY.height
19
- end
20
-
21
- def empty?
22
- @left == EMPTY.left && @top == EMPTY.top && @width == EMPTY.width && @height == EMPTY.height
23
- end
24
-
25
- def right
26
- left + width
27
- end
28
-
29
- def bottom
30
- top + height
31
- end
32
-
33
- def intersecting?(other)
34
- ((left <= other.left && other.left <= right) || (other.left <= left && left <= other.right)) &&
35
- ((top <= other.top && other.top <= bottom) || (other.top <= top && top <= other.bottom))
36
- end
37
-
38
- def intersect(other)
39
- unless intersecting?(other)
40
- make_empty
41
-
42
- return
43
- end
44
-
45
- i_left = left >= other.left ? left : other.left
46
- i_right = right <= other.right ? right : other.right
47
- i_top = top >= other.top ? top : other.top
48
- i_bottom = bottom <= other.bottom ? bottom : other.bottom
49
-
50
- @left = i_left
51
- @top = i_top
52
- @width = i_right - i_left
53
- @height = i_bottom - i_top
54
- end
55
-
56
- def contains?(other_left, other_top)
57
- other_left >= left && other_left <= right && other_top >= top && other_top <= bottom
58
- end
59
-
60
- def middle_offset
61
- mid_x = width / 2
62
- mid_y = height / 2
63
- Point.new(mid_x.round, mid_y.round)
64
- end
65
-
66
- def subregions(subregion_size)
67
- [].tap do |subregions|
68
- current_top = @top
69
- bottom = @top + @height
70
- right = @left + @width
71
- subregion_width = [@width, subregion_size.width].min
72
- subregion_height = [@height, subregion_size.height].min
73
-
74
- while current_top < bottom
75
- current_bottom = current_top + subregion_height
76
- if current_bottom > bottom
77
- current_bottom = bottom
78
- current_top = current_bottom - subregion_height
79
- end
80
-
81
- current_left = @left
82
- while current_left < right
83
- current_right = current_left + subregion_width
84
- if current_right > right
85
- current_right = right
86
- current_left = current_right - subregion_width
87
- end
88
-
89
- subregions << Region.new(current_left, current_top, subregion_width, subregion_height)
90
-
91
- current_left += subregion_width
92
- end
93
-
94
- current_top += subregion_height
95
- end
96
- end
97
- end
98
-
99
- def to_hash
100
- {
101
- left: left,
102
- top: top,
103
- height: height,
104
- width: width
105
- }
106
- end
107
-
108
- def to_s
109
- "(#{left}, #{top}), #{width} x #{height}"
110
- end
111
- end
112
- end
@@ -1,120 +0,0 @@
1
- require 'faraday'
2
-
3
- require 'oj'
4
- Oj.default_options = { :mode => :compat }
5
-
6
- require 'uri'
7
-
8
- module Applitools::Base::ServerConnector
9
- extend self
10
-
11
- DEFAULT_SERVER_URL = 'https://eyessdk.applitools.com'.freeze
12
-
13
- SSL_CERT = File.join(File.dirname(File.expand_path(__FILE__)), '../../../certs/cacert.pem').to_s.freeze
14
- DEFAULT_TIMEOUT = 300
15
-
16
- API_SESSIONS_RUNNING = '/api/sessions/running/'.freeze
17
-
18
- HTTP_STATUS_CODES = {
19
- created: 201,
20
- accepted: 202
21
- }.freeze
22
-
23
- attr_accessor :server_url, :api_key
24
- attr_reader :endpoint_url
25
-
26
- def server_url=(url)
27
- @server_url = url.nil? ? DEFAULT_SERVER_URL : url
28
- @endpoint_url = URI.join(@server_url, API_SESSIONS_RUNNING).to_s
29
- end
30
-
31
- def set_proxy(uri, user = nil, password = nil)
32
- @proxy = {
33
- uri: uri,
34
- user: user,
35
- password: password
36
- }
37
- end
38
-
39
- def match_window(session, data)
40
- # Notice that this does not include the screenshot.
41
- json_data = Oj.dump(Applitools::Utils.camelcase_hash_keys(data.to_hash)).force_encoding('BINARY')
42
- body = [json_data.length].pack('L>') + json_data + data.screenshot
43
- Applitools::EyesLogger.debug 'Sending match data...'
44
-
45
- res = post(URI.join(endpoint_url, session.id.to_s), content_type: 'application/octet-stream', body: body)
46
- raise Applitools::EyesError.new("Request failed: #{res.status}") unless res.success?
47
-
48
- Oj.load(res.body)['asExpected'].tap do |as_expected|
49
- Applitools::EyesLogger.debug "Got response! #{as_expected}"
50
- end
51
- end
52
-
53
- def start_session(session_start_info)
54
- res = post(endpoint_url, body: Oj.dump(startInfo:
55
- Applitools::Utils.camelcase_hash_keys(session_start_info.to_hash)))
56
- raise Applitools::EyesError.new("Request failed: #{res.status}") unless res.success?
57
-
58
- response = Oj.load(res.body)
59
- Applitools::Base::Session.new(response['id'], response['url'], res.status == HTTP_STATUS_CODES[:created])
60
- end
61
-
62
- def stop_session(session, aborted = nil, save = false)
63
- res = long_delete(URI.join(endpoint_url, session.id.to_s), query: { aborted: aborted, updateBaseline: save })
64
- raise Applitools::EyesError.new("Request failed: #{res.status}") unless res.success?
65
-
66
- response = Oj.load(res.body)
67
- Applitools::Base::TestResults.new(response)
68
- end
69
-
70
- private
71
-
72
- DEFAULT_HEADERS = {
73
- 'Accept' => 'application/json',
74
- 'Content-Type' => 'application/json'
75
- }.freeze
76
-
77
- LONG_REQUEST_DELAY = 2 # seconds
78
- MAX_LONG_REQUEST_DELAY = 10 # seconds
79
- LONG_REQUEST_DELAY_MULTIPLICATIVE_INCREASE_FACTOR = 1.5
80
-
81
- [:get, :post, :delete].each do |method|
82
- define_method method do |url, options = {}|
83
- request(url, method, options)
84
- end
85
-
86
- define_method "long_#{method}" do |url, options = {}|
87
- long_request(url, method, options)
88
- end
89
- end
90
-
91
- def request(url, method, options = {})
92
- Faraday::Connection.new(url, ssl: { ca_file: SSL_CERT }, proxy: @proxy || nil).send(method) do |req|
93
- req.options.timeout = DEFAULT_TIMEOUT
94
- req.headers = DEFAULT_HEADERS.merge(options[:headers] || {})
95
- req.headers['Content-Type'] = options[:content_type] if options.key?(:content_type)
96
- req.params = { apiKey: api_key }.merge(options[:query] || {})
97
- req.body = options[:body]
98
- end
99
- end
100
-
101
- def long_request(url, method, options = {})
102
- delay = LONG_REQUEST_DELAY
103
- (options[:headers] ||= {})['Eyes-Expect'] = '202-accepted'
104
-
105
- loop do
106
- # Date should be in RFC 1123 format.
107
- options[:headers]['Eyes-Date'] = Time.now.utc.strftime('%a, %d %b %Y %H:%M:%S GMT')
108
-
109
- res = request(url, method, options)
110
- return res unless res.status == HTTP_STATUS_CODES[:accepted]
111
-
112
- Applitools::EyesLogger.debug "Still running... retrying in #{delay}s"
113
- sleep delay
114
-
115
- delay = [MAX_LONG_REQUEST_DELAY, (delay * LONG_REQUEST_DELAY_MULTIPLICATIVE_INCREASE_FACTOR).round].min
116
- end
117
- end
118
-
119
- include Applitools::MethodTracer
120
- end
@@ -1,15 +0,0 @@
1
- module Applitools::Base
2
- class Session
3
- attr_reader :id, :url
4
-
5
- def initialize(session_id, session_url, new_session)
6
- @id = session_id
7
- @url = session_url
8
- @new_session = new_session
9
- end
10
-
11
- def new_session?
12
- @new_session
13
- end
14
- end
15
- end
@@ -1,34 +0,0 @@
1
- module Applitools::Base
2
- class StartInfo
3
- attr_accessor :app_id_or_name, :scenario_id_or_name
4
-
5
- def initialize(agent_id, app_id_or_name, scenario_id_or_name, batch_info, env_name, environment, match_level,
6
- ver_id = nil, branch_name = nil, parent_branch_name = nil)
7
- @agent_id = agent_id
8
- @app_id_or_name = app_id_or_name
9
- @ver_id = ver_id
10
- @scenario_id_or_name = scenario_id_or_name
11
- @batch_info = batch_info
12
- @env_name = env_name
13
- @environment = environment
14
- @match_level = match_level
15
- @branch_name = branch_name
16
- @parent_branch_name = parent_branch_name
17
- end
18
-
19
- def to_hash
20
- {
21
- agent_id: @agent_id,
22
- app_id_or_name: @app_id_or_name,
23
- ver_id: @ver_id,
24
- scenario_id_or_name: @scenario_id_or_name,
25
- batch_info: @batch_info.to_hash,
26
- env_name: @env_name,
27
- environment: @environment.to_hash,
28
- match_level: @match_level,
29
- branch_name: @branch_name,
30
- parent_branch_name: @parent_branch_name
31
- }
32
- end
33
- end
34
- end
@@ -1,28 +0,0 @@
1
- module Applitools::Base
2
- class TestResults
3
- attr_accessor :is_new, :url
4
- attr_reader :steps, :matches, :mismatches, :missing
5
-
6
- def initialize(results = {})
7
- @steps = results.fetch('steps', 0)
8
- @matches = results.fetch('matches', 0)
9
- @mismatches = results.fetch('mismatches', 0)
10
- @missing = results.fetch('missing', 0)
11
- @is_new = nil
12
- @url = nil
13
- end
14
-
15
- def passed?
16
- !is_new && !(mismatches > 0) && !(missing > 0)
17
- end
18
- alias is_passed passed?
19
-
20
- def to_s
21
- is_new_str = ''
22
- is_new_str = is_new ? 'New test' : 'Existing test' unless is_new.nil?
23
-
24
- "#{is_new_str} [ steps: #{steps}, matches: #{matches}, mismatches: #{mismatches}, missing: #{missing} ], "\
25
- "URL: #{url}"
26
- end
27
- end
28
- end
@@ -1,22 +0,0 @@
1
- module Applitools::Base
2
- class TextTrigger
3
- attr_reader :text, :control
4
-
5
- def initialize(text, control)
6
- @text = text
7
- @control = control
8
- end
9
-
10
- def to_hash
11
- {
12
- trigget_type: 'Text',
13
- text: text,
14
- control: control.to_hash
15
- }
16
- end
17
-
18
- def to_s
19
- "Text [#{@control}] #{@text}"
20
- end
21
- end
22
- end
@@ -1,17 +0,0 @@
1
- class Module
2
- def alias_attribute(new_name, old_name)
3
- module_eval <<-STR, __FILE__, __LINE__ + 1
4
- def #{new_name}
5
- self.#{old_name}
6
- end
7
-
8
- def #{new_name}?
9
- self.#{old_name}?
10
- end
11
-
12
- def #{new_name}=(v)
13
- self.#{old_name}
14
- end
15
- STR
16
- end
17
- end
@@ -1,460 +0,0 @@
1
- require 'capybara/poltergeist'
2
-
3
- require_relative 'version'
4
- require_relative 'eyes_logger'
5
-
6
- require 'forwardable'
7
-
8
- class Applitools::Eyes
9
- extend Forwardable
10
-
11
- FAILURE_REPORTS = {
12
- immediate: 'Immediate',
13
- on_close: 'OnClose'
14
- }.freeze
15
-
16
- MATCH_LEVEL = {
17
- none: 'None',
18
- layout: 'Layout',
19
- layout2: 'Layout2',
20
- content: 'Content',
21
- strict: 'Strict',
22
- exact: 'Exact'
23
- }.freeze
24
-
25
- DEFAULT_MATCH_TIMEOUT = 2.0 # Seconds
26
- # noinspection RubyConstantNamingConvention
27
- DEFAULT_WAIT_BEFORE_SCREENSHOTS = 0.1 # Seconds
28
- BASE_AGENT_ID = ('eyes.selenium.ruby/' + Applitools::VERSION).freeze
29
-
30
- ANDROID = 'Android'.freeze
31
- IOS = 'iOS'.freeze
32
-
33
- # Attributes:
34
- #
35
- # +app_name+:: +String+ The application name which was provided as an argument to +open+.
36
- # +test_name+:: +String+ The test name which was provided as an argument to +open+.
37
- # +is_open+:: +boolean+ Is there an open session.
38
- # +viewport_size+:: +Hash+ The viewport size which was provided as an argument to +open+. Should include +width+
39
- # and +height+.
40
- # +driver+:: +Applitools::Selenium::Driver+ The driver instance wrapping the driver which was provided as an argument
41
- # to +open+.
42
- # +api_key+:: +String+ The user's API key.
43
- # +match_timeout+:: +Float+ The default timeout for check_XXXX operations. (Seconds)
44
- # +batch+:: +BatchInfo+ The current tests grouping, if any.
45
- # +host_os+:: +String+ A string identifying the OS running the AUT. Set this if you wish to override Eyes' automatic
46
- # inference.
47
- # +host_app+:: +String+ A string identifying the container application running the AUT (e.g., Firefox). Set this if
48
- # you wish to override Eyes' automatic inference.
49
- # +branch_name+:: +String+ If set, names the branch in which the test should run.
50
- # +parent_branch_name+:: +String+ If set, names the parent branch of the branch in which the test should run.
51
- # +user_inputs+:: +Applitools::Base::MouseTrigger+/+Applitools::Selenium::KeyboardTrigger+ Mouse/Keyboard events which
52
- # happened after the last visual validation.
53
- # +save_new_tests+:: +boolean+ Whether or not new tests should be automatically accepted as baseline.
54
- # +save_failed_tests+:: +boolean+ Whether or not failed tests should be automatically accepted as baseline.
55
- # +match_level+:: +String+ The default match level for the entire session. See +Applitools::Eyes::MATCH_LEVEL+.
56
- # +baseline_name+:: +String+ A string identifying the baseline which the test will be compared against. Set this if
57
- # you wish to override Eyes' automatic baseline inference.
58
- # +is_disabled+:: +boolean+ Set to +true+ if you wish to disable Eyes without deleting code (Eyes' methods act as a
59
- # mock, and will do nothing).
60
- # +server_url+:: +String+ The Eyes' server. Set this if you wish to override the default Eyes server URL.
61
- # +agent_id+:: +String+ An optional string identifying the current library using the SDK.
62
- # +log_handler+:: +Logger+ The logger to which Eyes will send info/debug messages.
63
- # +failure_reports+:: +String+ Whether the current test will report mismatches immediately or when it is finished.
64
- # See +Applitools::Eyes::FAILURE_REPORTS+.
65
- # +rotation+:: +Integer+|+nil+ The degrees by which to rotate the screenshots received from the driver. Set this to
66
- # override Eyes' automatic rotation inference. Positive values = clockwise rotation, negative
67
- # values = counter-clockwise, 0 = force no rotation, +nil+ = use Eyes' automatic rotation inference.
68
- # +scale_ratio+:: +Float+|+nil+ The ratio by which to scale the screenshots received from the driver. Set this to
69
- # override Eyes' automatic scaling inference. Values must be >=0 or +nil+. 1 = Don't scale, +nil+ = use Eyes'
70
- # automatic scaling inference.
71
- # +force_fullpage_screenshot+:: +boolean+ Whether or not to force fullpage screenshot taking, if the browser doesn't
72
- # support it explicitly.
73
- # +hide_scrollbars+:: +boolean+ Whether or not hide scrollbars.
74
- # +use_css_transition+:: +boolean+ Whether or not to perform CSS transition.
75
- # +wait_before_screenshot+:: +Integer+ The number of milliseconds to wait before each screenshot. Use -1 to reset to
76
- # the default value.
77
- # +debug_screenshot+:: +boolean+ If true saves every taken screenshot in current folder. File name has following
78
- # format: +TAG_YYYY_MM_DD_HH_MI__N.png+, where +TAG+ - the tag specified for the test,
79
- # +YYYY_MM_DD_HH_MI+ - date && time, +N+ - screenshot number (makes sense only when
80
- # +force_fullpage_screenshot+ is true). Default value is false
81
-
82
- attr_reader :app_name, :test_name, :is_open, :viewport_size, :driver, :passed_driver
83
- attr_accessor :match_timeout, :batch, :host_os, :host_app, :branch_name, :parent_branch_name, :user_inputs,
84
- :save_new_tests, :save_failed_tests, :is_disabled, :server_url, :agent_id, :failure_reports,
85
- :match_level, :baseline_name, :rotation, :force_fullpage_screenshot, :hide_scrollbars,
86
- :use_css_transition, :scale_ratio, :wait_before_screenshots, :debug_screenshot
87
-
88
- def_delegators 'Applitools::EyesLogger', :log_handler, :log_handler=
89
- def_delegators 'Applitools::Base::ServerConnector', :api_key, :api_key=, :server_url, :server_url=, :set_proxy
90
-
91
- def wait_before_screenshots=(ms)
92
- @wait_before_screenshots = ms > 0 ? (ms / 1000.0) : DEFAULT_WAIT_BEFORE_SCREENSHOTS
93
- end
94
-
95
- def full_agent_id
96
- @full_agent_id ||= agent_id.nil? ? BASE_AGENT_ID : "#{agent_id} [#{BASE_AGENT_ID}]"
97
- end
98
-
99
- def title
100
- unless @dont_get_title
101
- begin
102
- return driver.title
103
- rescue
104
- @dont_get_title = true
105
- end
106
- end
107
-
108
- ''
109
- end
110
-
111
- def initialize(options = {})
112
- @is_disabled = false
113
- @debug_screenshot = options[:debug_screenshot].nil? ? false : true
114
- return if disabled?
115
-
116
- @api_key = nil
117
- @user_inputs = []
118
-
119
- Applitools::Base::ServerConnector.server_url = options[:server_url]
120
-
121
- @match_timeout = DEFAULT_MATCH_TIMEOUT
122
- @match_level = Applitools::Eyes::MATCH_LEVEL[:exact]
123
- @failure_reports = Applitools::Eyes::FAILURE_REPORTS[:on_close]
124
- @save_new_tests = true
125
- @save_failed_tests = false
126
- @dont_get_title = false
127
- @force_fullpage_screenshot = false
128
- @hide_scrollbars = false
129
- @use_css_transition = false
130
- @wait_before_screenshots = DEFAULT_WAIT_BEFORE_SCREENSHOTS
131
- end
132
-
133
- def open(options = {})
134
- @passed_driver = @driver = get_driver(options.merge(debug_screenshot: debug_screenshot))
135
- return driver if disabled?
136
-
137
- if api_key.nil?
138
- raise Applitools::EyesError.new('API key not set! Log in to https://applitools.com to obtain your API Key and '\
139
- "use 'api_key' to set it.")
140
- end
141
-
142
- if driver.respond_to? :driver_for_eyes
143
- @driver = driver.driver_for_eyes self
144
- else
145
- unless driver.is_a?(Applitools::Selenium::Driver)
146
- is_mobile_device = driver.respond_to?(:capabilities) && driver.capabilities['platformName']
147
-
148
- @driver =
149
- case driver
150
- when Selenium::WebDriver
151
- Applitools::Selenium::Driver.new(self, driver: driver, is_mobile_device: is_mobile_device)
152
- when Capybara::Poltergeist::Driver # driver for PhantomJS
153
- Applitools::Poltergeist::Driver.new(self, driver: driver, is_mobile_device: is_mobile_device)
154
- else
155
- Applitools::EyesLogger.warn("Unrecognized driver type: (#{driver.class.name})!")
156
- Applitools::Selenium::Driver.new(self, driver: driver, is_mobile_device: is_mobile_device)
157
- end
158
-
159
- end
160
- end
161
-
162
- @driver.wait_before_screenshots = wait_before_screenshots
163
-
164
- if open?
165
- abort_if_not_closed
166
- msg = 'a test is already running'
167
- Applitools::EyesLogger.warn(msg)
168
-
169
- raise Applitools::EyesError.new(msg)
170
- end
171
-
172
- @user_inputs = []
173
- @app_name = options.fetch(:app_name)
174
- if @app_name.nil? || @app_name.empty?
175
- raise Applitools::EyesError.new('App name must be a non empty string.')
176
- end
177
- @test_name = options.fetch(:test_name)
178
- if @test_name.nil? || @test_name.empty?
179
- raise Applitools::EyesError.new('Test name must be a non empty string.')
180
- end
181
- @viewport_size = options.fetch(:viewport_size, nil)
182
-
183
- @is_open = true
184
-
185
- driver
186
- end
187
-
188
- def open?
189
- is_open
190
- end
191
-
192
- def clear_user_inputs
193
- user_inputs.clear
194
- end
195
-
196
- def check_region(how, what, tag = nil, specific_timeout = -1)
197
- Applitools::EyesLogger.debug 'check_region called'
198
- return if disabled?
199
-
200
- # We have to start the session if it's not started, since we want the viewport size to be set before getting the
201
- # element's position and size
202
- raise Applitools::EyesError.new('Eyes not open') unless open?
203
-
204
- unless @session
205
- Applitools::EyesLogger.debug 'Starting session...'
206
- start_session
207
- Applitools::EyesLogger.debug 'Done! Creating match window task...'
208
- @match_window_task = Applitools::Selenium::MatchWindowTask.new(self, @session, driver, match_timeout)
209
- Applitools::EyesLogger.debug 'Done!'
210
- end
211
-
212
- original_overflow = driver.hide_scrollbars if hide_scrollbars
213
- begin
214
- if how == :element
215
- Applitools::EyesLogger.debug 'Element given as an argument...'
216
- raise Applitools::EyesError.new('Element does not exist') if what.nil?
217
- element_to_check = what
218
- elsif how == :region && what.is_a?(Applitools::Base::Region)
219
- return check_region_(what, tag, specific_timeout)
220
- else
221
- Applitools::EyesLogger.debug 'Finding element...'
222
- element_to_check = driver.find_element(how, what)
223
- end
224
-
225
- Applitools::EyesLogger.debug 'Done! Getting element location...'
226
- location = element_to_check.location
227
- Applitools::EyesLogger.debug 'Done! Getting element size...'
228
- size = element_to_check.size
229
- raise Applitools::EyesError.new("Invalid region size: #{size}") if size.width <= 0 || size.height <= 0
230
- Applitools::EyesLogger.debug 'Done! Creating region...'
231
- region = Applitools::Base::Region.new(location.x, location.y, size.width, size.height)
232
- Applitools::EyesLogger.debug "Done! Checking region... #{region}"
233
- check_region_(region, tag, specific_timeout)
234
- Applitools::EyesLogger.debug 'Done!'
235
- ensure
236
- driver.set_overflow(original_overflow) if hide_scrollbars
237
- end
238
- end
239
-
240
- def check_window(tag = nil, specific_timeout = -1)
241
- original_overflow = driver.hide_scrollbars if hide_scrollbars
242
- begin
243
- check_region_(Applitools::Base::Region::EMPTY, tag, specific_timeout)
244
- ensure
245
- driver.set_overflow(original_overflow) if hide_scrollbars
246
- end
247
- end
248
-
249
- def close(raise_ex = true)
250
- return if disabled?
251
- @is_open = false
252
- passed_driver.use_native_browser if passed_driver.respond_to? :use_native_browser
253
-
254
- # If there's no running session, the test was never started (never reached check_window).
255
- unless @session
256
- Applitools::EyesLogger.debug 'Server session was not started'
257
- Applitools::EyesLogger.info '--- Empty test ended.'
258
-
259
- return Applitools::Base::TestResults.new
260
- end
261
-
262
- session_results_url = @session.url
263
- new_session = @session.new_session?
264
- Applitools::EyesLogger.debug 'Ending server session...'
265
- save = (new_session && save_new_tests) || (!new_session && save_failed_tests)
266
- results = Applitools::Base::ServerConnector.stop_session(@session, false, save)
267
- results.is_new = new_session
268
- results.url = session_results_url
269
- Applitools::EyesLogger.debug "Results: #{results}"
270
-
271
- @session = nil
272
-
273
- if new_session
274
- instructions = "Please approve the new baseline at #{session_results_url}"
275
- Applitools::EyesLogger.info "--- New test ended. #{instructions}"
276
-
277
- if raise_ex
278
- message = "'#{@session_start_info.scenario_id_or_name}' of '#{@session_start_info.app_id_or_name}'. "\
279
- "#{instructions}"
280
-
281
- raise Applitools::NewTestError.new(message, results)
282
- end
283
-
284
- return results
285
- end
286
-
287
- unless results.passed?
288
- # Test failed
289
- Applitools::EyesLogger.info "--- Failed test ended. See details at #{session_results_url}"
290
-
291
- if raise_ex
292
- message = "'#{@session_start_info.scenario_id_or_name}' of '#{@session_start_info.app_id_or_name}'. see "\
293
- "details at #{session_results_url}"
294
-
295
- raise Applitools::TestFailedError.new(message, results)
296
- end
297
-
298
- return results
299
- end
300
-
301
- # Test passed
302
- Applitools::EyesLogger.info "--- Test passed. See details at #{session_results_url}"
303
-
304
- results
305
- end
306
-
307
- ## Use this method to perform seamless testing with selenium through eyes driver.
308
- ## Using Selenium methods inside the 'test' block will send the messages to Selenium
309
- ## after creating the Eyes triggers for them.
310
- ##
311
- ## Example:
312
- # eyes.test(app_name: 'my app1', test_name: 'my test') do |d|
313
- # get "http://www.google.com"
314
- # check_window("initial")
315
- # end
316
- # noinspection RubyUnusedLocalVariable
317
- def test(options = {}, &_block)
318
- open(options)
319
- yield(driver)
320
- close
321
- ensure
322
- abort_if_not_closed
323
- end
324
-
325
- def abort_if_not_closed
326
- return if disabled?
327
-
328
- @is_open = false
329
- passed_driver.use_native_browser if passed_driver.respond_to? :use_native_browser
330
-
331
- return unless @session
332
-
333
- begin
334
- Applitools::Base::ServerConnector.stop_session(@session, true, false)
335
- rescue => e
336
- Applitools::EyesLogger.error "Failed to abort server session: #{e.message}!"
337
- ensure
338
- @session = nil
339
- end
340
- end
341
-
342
- private
343
-
344
- def disabled?
345
- is_disabled
346
- end
347
-
348
- def get_driver(options)
349
- # TODO: remove the "browser" related block when possible. It's for backward compatibility.
350
- if options.key?(:browser)
351
- Applitools::EyesLogger.warn('"browser" key is deprecated, please use "driver" instead.')
352
-
353
- return options[:browser]
354
- end
355
-
356
- options.fetch(:driver, nil)
357
- end
358
-
359
- def inferred_environment
360
- user_agent = driver.user_agent
361
- "useragent:#{user_agent}" if user_agent
362
- end
363
-
364
- # Application environment is the environment (e.g., the host OS) which runs the application under test.
365
- #
366
- # Returns:
367
- # +Applitools::Base::Environment+ The application environment.
368
- def app_environment
369
- os = host_os
370
- if os.nil?
371
- Applitools::EyesLogger.info 'No OS set, checking for mobile OS...'
372
- if driver.mobile_device?
373
- platform_name = nil
374
- Applitools::EyesLogger.info 'Mobile device detected! Checking device type..'
375
- if driver.android?
376
- Applitools::EyesLogger.info 'Android detected.'
377
- platform_name = ANDROID
378
- elsif driver.ios?
379
- Applitools::EyesLogger.info 'iOS detected.'
380
- platform_name = IOS
381
- else
382
- Applitools::EyesLogger.warn 'Unknown device type.'
383
- end
384
- # We only set the OS if we identified the device type.
385
- unless platform_name.nil?
386
- platform_version = driver.platform_version
387
- if platform_version.nil?
388
- os = platform_name
389
- else
390
- # Notice that Ruby's +split+ function's +limit+ is the number of elements, whereas in Python it is the
391
- # maximum splits performed (which is why they are set differently).
392
- major_version = platform_version.split('.', 2)[0]
393
- os = "#{platform_name} #{major_version}"
394
- end
395
- Applitools::EyesLogger.info "Setting OS: #{os}"
396
- end
397
- else
398
- Applitools::EyesLogger.info 'No mobile OS detected.'
399
- end
400
- end
401
-
402
- # Create and return the environment object.
403
- Applitools::Base::Environment.new(os, host_app, viewport_size, inferred_environment)
404
- end
405
-
406
- def start_session
407
- assign_viewport_size
408
- @batch ||= Applitools::Base::BatchInfo.new
409
- app_env = app_environment
410
-
411
- @session_start_info = Applitools::Base::StartInfo.new(full_agent_id, app_name, test_name, batch, baseline_name,
412
- app_env, match_level, nil, branch_name, parent_branch_name)
413
- @session = Applitools::Base::ServerConnector.start_session(@session_start_info)
414
- @should_match_window_run_once_on_timeout = @session.new_session?
415
- end
416
-
417
- def viewport_size?
418
- viewport_size
419
- end
420
-
421
- def assign_viewport_size
422
- if viewport_size?
423
- @viewport_size = Applitools::Selenium::ViewportSize.new(driver, viewport_size)
424
- @viewport_size.set
425
- else
426
- @viewport_size = Applitools::Selenium::ViewportSize.new(driver)
427
- @viewport_size.extract_viewport_from_browser!
428
- end
429
- end
430
-
431
- def check_region_(region, tag = nil, specific_timeout = -1)
432
- return if disabled?
433
- Applitools::EyesLogger.info "check_region_('#{tag}', #{specific_timeout})"
434
- raise Applitools::EyesError.new('region cannot be nil!') if region.nil?
435
- raise Applitools::EyesError.new('Eyes not open') unless open?
436
-
437
- unless @session
438
- Applitools::EyesLogger.debug 'Starting session...'
439
- start_session
440
- Applitools::EyesLogger.debug 'Done! Creating match window task...'
441
- @match_window_task = Applitools::Selenium::MatchWindowTask.new(self, @session, driver, match_timeout)
442
- Applitools::EyesLogger.debug 'Done!'
443
- end
444
-
445
- Applitools::EyesLogger.debug 'Starting match task...'
446
- as_expected = @match_window_task.match_window(region, specific_timeout, tag, rotation,
447
- @should_match_window_run_once_on_timeout)
448
- Applitools::EyesLogger.debug 'Match window done!'
449
- return if as_expected
450
-
451
- @should_match_window_run_once_on_timeout = true
452
- return if @session.new_session?
453
-
454
- Applitools::EyesLogger.info %(mismatch #{tag ? '' : "(#{tag})"})
455
- return unless failure_reports.to_i == Applitools::Eyes::FAILURE_REPORTS[:immediate]
456
-
457
- raise Applitools::TestFailedError.new("Mismatch found in '#{@session_start_info.scenario_id_or_name}' "\
458
- "of '#{@session_start_info.app_id_or_name}'")
459
- end
460
- end