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.
- checksums.yaml +4 -4
- data/lib/applitools/capybara.rb +1 -1
- data/lib/applitools/selenium/border_aware_element_content_location_provider.rb +41 -0
- data/lib/applitools/selenium/browser.rb +1 -0
- data/lib/applitools/selenium/capybara/capybara_settings.rb +8 -0
- data/lib/applitools/selenium/capybara/driver.rb +1 -0
- data/lib/applitools/selenium/context_based_scale_provider.rb +36 -0
- data/lib/applitools/selenium/css_translate_position_provider.rb +66 -0
- data/lib/applitools/selenium/driver.rb +159 -44
- data/lib/applitools/selenium/element.rb +100 -8
- data/lib/applitools/selenium/element_position_provider.rb +48 -0
- data/lib/applitools/selenium/eyes.rb +922 -0
- data/lib/applitools/selenium/eyes_target_locator.rb +191 -0
- data/lib/applitools/selenium/eyes_web_driver_screenshot.rb +274 -0
- data/lib/applitools/selenium/frame.rb +24 -0
- data/lib/applitools/selenium/frame_chain.rb +68 -0
- data/lib/applitools/selenium/full_page_capture_algorithm.rb +162 -0
- data/lib/applitools/selenium/keyboard.rb +1 -0
- data/lib/applitools/selenium/match_window_task.rb +0 -197
- data/lib/applitools/selenium/mouse.rb +1 -0
- data/lib/applitools/selenium/move_to_region_visibility_strategy.rb +33 -0
- data/lib/applitools/selenium/nop_region_visibility_strategy.rb +16 -0
- data/lib/applitools/selenium/scroll_position_provider.rb +52 -0
- data/lib/applitools/selenium/takes_screenshot_image_provider.rb +36 -0
- data/lib/applitools/version.rb +1 -1
- data/lib/eyes_selenium.rb +19 -30
- metadata +24 -254
- data/.gitignore +0 -18
- data/.rspec +0 -2
- data/.rubocop.yml +0 -57
- data/.travis.yml +0 -17
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -13
- data/README.md +0 -38
- data/Rakefile +0 -11
- data/certs/cacert.pem +0 -3557
- data/examples/appium_example_script.rb +0 -59
- data/examples/capybara_example.rb +0 -82
- data/examples/sauce_capybara_example.rb +0 -41
- data/examples/sauce_example.rb +0 -34
- data/examples/simple_test_script.rb +0 -23
- data/examples/watir_test_script.rb +0 -26
- data/eyes_selenium.gemspec +0 -89
- data/lib/applitools/appium_driver.rb +0 -7
- data/lib/applitools/base/batch_info.rb +0 -21
- data/lib/applitools/base/dimension.rb +0 -36
- data/lib/applitools/base/environment.rb +0 -19
- data/lib/applitools/base/image_position.rb +0 -10
- data/lib/applitools/base/mouse_trigger.rb +0 -33
- data/lib/applitools/base/point.rb +0 -34
- data/lib/applitools/base/region.rb +0 -112
- data/lib/applitools/base/server_connector.rb +0 -120
- data/lib/applitools/base/session.rb +0 -15
- data/lib/applitools/base/start_info.rb +0 -34
- data/lib/applitools/base/test_results.rb +0 -28
- data/lib/applitools/base/text_trigger.rb +0 -22
- data/lib/applitools/extensions.rb +0 -17
- data/lib/applitools/eyes.rb +0 -460
- data/lib/applitools/eyes_logger.rb +0 -38
- data/lib/applitools/method_tracer.rb +0 -22
- data/lib/applitools/poltergeist/applitools_compatible.rb +0 -28
- data/lib/applitools/poltergeist/driver.rb +0 -11
- data/lib/applitools/sauce.rb +0 -2
- data/lib/applitools/selenium/match_window_data.rb +0 -28
- data/lib/applitools/selenium_webdriver.rb +0 -8
- data/lib/applitools/utils/image_delta_compressor.rb +0 -148
- data/lib/applitools/utils/image_utils.rb +0 -143
- data/lib/applitools/utils/utils.rb +0 -49
- data/lib/applitools/watir_browser.rb +0 -8
- data/spec/driver_passthrough_spec.rb +0 -68
- data/spec/fixtures/static_test_file.html +0 -15
- data/spec/spec_helper.rb +0 -17
@@ -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
|
data/lib/applitools/eyes.rb
DELETED
@@ -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
|