eyes_selenium 3.14.10 → 3.15.0.beta
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.
- checksums.yaml +4 -4
- data/lib/applitools/selenium/concerns/browser_types.rb +15 -0
- data/lib/applitools/selenium/concerns/browsers_info.rb +16 -0
- data/lib/applitools/selenium/concerns/external_css_resources.rb +31 -0
- data/lib/applitools/selenium/concerns/render_browser_info_fluent.rb +42 -0
- data/lib/applitools/selenium/concerns/render_resources.rb +15 -0
- data/lib/applitools/selenium/concerns/rgrid_dom.rb +46 -0
- data/lib/applitools/selenium/concerns/stitch_modes.rb +15 -0
- data/lib/applitools/selenium/concerns/test_list.rb +23 -0
- data/lib/applitools/selenium/eyes.rb +13 -854
- data/lib/applitools/selenium/scripts/process_page_and_serialize.rb +519 -0
- data/lib/applitools/selenium/selenium_configuration.rb +64 -0
- data/lib/applitools/selenium/selenium_eyes.rb +860 -0
- data/lib/applitools/selenium/target.rb +2 -2
- data/lib/applitools/selenium/visual_grid/eyes_connector.rb +170 -0
- data/lib/applitools/selenium/visual_grid/render_browser_info.rb +31 -0
- data/lib/applitools/selenium/visual_grid/render_info.rb +9 -0
- data/lib/applitools/selenium/visual_grid/render_request.rb +22 -0
- data/lib/applitools/selenium/visual_grid/render_requests.rb +11 -0
- data/lib/applitools/selenium/visual_grid/render_task.rb +171 -0
- data/lib/applitools/selenium/visual_grid/resource_cache.rb +51 -0
- data/lib/applitools/selenium/visual_grid/running_test.rb +198 -0
- data/lib/applitools/selenium/visual_grid/thread_pool.rb +94 -0
- data/lib/applitools/selenium/visual_grid/vg_resource.rb +37 -0
- data/lib/applitools/selenium/visual_grid/vg_task.rb +46 -0
- data/lib/applitools/selenium/visual_grid/visual_grid_eyes.rb +236 -0
- data/lib/applitools/selenium/visual_grid/visual_grid_runner.rb +57 -0
- data/lib/applitools/version.rb +1 -1
- data/lib/eyes_selenium.rb +3 -0
- metadata +46 -7
@@ -0,0 +1,170 @@
|
|
1
|
+
module Applitools
|
2
|
+
module Selenium
|
3
|
+
class EyesConnector < ::Applitools::EyesBase
|
4
|
+
USE_DEFAULT_MATCH_TIMEOUT = -1
|
5
|
+
|
6
|
+
attr_accessor :browser_info, :test_result, :driver, :dummy_region_provider, :dont_get_title,
|
7
|
+
:current_uuid, :render_statuses
|
8
|
+
public :server_connector
|
9
|
+
|
10
|
+
class RegionProvider
|
11
|
+
def region
|
12
|
+
Applitools::Region::EMPTY
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
super
|
18
|
+
self.render_statuses = {}
|
19
|
+
self.dummy_region_provider = RegionProvider.new
|
20
|
+
self.dont_get_title = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def ensure_config
|
24
|
+
self.config = Applitools::Selenium::SeleniumConfiguration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def open(driver, browser_info)
|
29
|
+
self.driver = driver
|
30
|
+
self.browser_info = browser_info
|
31
|
+
logger.info "opening EyesConnector for #{config.short_description} with viewport size: #{browser_info}"
|
32
|
+
config.viewport_size = browser_info.viewport_size
|
33
|
+
open_base
|
34
|
+
# ensure_running_session
|
35
|
+
end
|
36
|
+
|
37
|
+
def check(name, target, check_task_uuid)
|
38
|
+
self.current_uuid = check_task_uuid
|
39
|
+
target_to_check = target.finalize
|
40
|
+
timeout = target_to_check.options[:timeout] || USE_DEFAULT_MATCH_TIMEOUT
|
41
|
+
|
42
|
+
match_data = Applitools::MatchWindowData.new
|
43
|
+
match_data.tag = name
|
44
|
+
update_default_settings(match_data)
|
45
|
+
match_data.read_target(target_to_check, driver)
|
46
|
+
|
47
|
+
check_result = check_window_base(
|
48
|
+
dummy_region_provider, timeout, match_data
|
49
|
+
)
|
50
|
+
self.current_uuid = nil
|
51
|
+
check_result
|
52
|
+
end
|
53
|
+
|
54
|
+
def base_agent_id
|
55
|
+
"eyes.selenium.visualgrid.ruby/#{Applitools::VERSION}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def close(throw_exception = true, be_silent = false)
|
59
|
+
self.current_uuid = nil
|
60
|
+
self.test_result = super
|
61
|
+
end
|
62
|
+
|
63
|
+
def capture_screenshot
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_status_for_task(uuid, status)
|
68
|
+
render_statuses[uuid] = status.first
|
69
|
+
end
|
70
|
+
|
71
|
+
def render_status
|
72
|
+
status = render_statuses[current_uuid]
|
73
|
+
raise Applitools::EyesError, 'Got empty render status!' if status.nil? || !status.is_a?(Hash) || status.keys.empty?
|
74
|
+
status
|
75
|
+
end
|
76
|
+
|
77
|
+
def screenshot_url
|
78
|
+
render_status['imageLocation']
|
79
|
+
end
|
80
|
+
|
81
|
+
def dom_url
|
82
|
+
render_status['domLocation']
|
83
|
+
end
|
84
|
+
|
85
|
+
def match_level_keys
|
86
|
+
%w(match_level exact scale remainder).map(&:to_sym)
|
87
|
+
end
|
88
|
+
|
89
|
+
def update_default_settings(match_data)
|
90
|
+
match_level_keys.each do |k|
|
91
|
+
match_data.send("#{k}=", default_match_settings[k])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def inferred_environment
|
96
|
+
"useragent: #{render_status['userAgent']}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def default_match_settings
|
100
|
+
{
|
101
|
+
match_level: match_level,
|
102
|
+
exact: exact,
|
103
|
+
scale: server_scale,
|
104
|
+
remainder: server_remainder
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def set_viewport_size(*_args)
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
def title
|
113
|
+
return driver.title unless dont_get_title
|
114
|
+
rescue StandardError => e
|
115
|
+
logger.warn "failed (#{e.message})"
|
116
|
+
self.dont_get_title = false
|
117
|
+
''
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_app_output_with_screenshot(region_provider, last_screenshot)
|
121
|
+
dom_url = ''
|
122
|
+
# captured_dom_data = dom_data
|
123
|
+
# unless captured_dom_data.empty?
|
124
|
+
# begin
|
125
|
+
# logger.info 'Processing DOM..'
|
126
|
+
# dom_url = server_connector.post_dom_json(captured_dom_data) do |json|
|
127
|
+
# io = StringIO.new
|
128
|
+
# gz = Zlib::GzipWriter.new(io)
|
129
|
+
# gz.write(json.encode('UTF-8'))
|
130
|
+
# gz.close
|
131
|
+
# result = io.string
|
132
|
+
# io.close
|
133
|
+
# result
|
134
|
+
# end
|
135
|
+
# logger.info 'Done'
|
136
|
+
# logger.info dom_url
|
137
|
+
# rescue Applitools::EyesError => e
|
138
|
+
# logger.warn e.message
|
139
|
+
# dom_url = nil
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
# logger.info 'Getting screenshot...'
|
143
|
+
# screenshot = capture_screenshot
|
144
|
+
# logger.info 'Done getting screenshot!'
|
145
|
+
region = region_provider.region
|
146
|
+
|
147
|
+
# unless region.empty?
|
148
|
+
# screenshot = screenshot.sub_screenshot region, region_provider.coordinate_type, false
|
149
|
+
# end
|
150
|
+
|
151
|
+
# screenshot = yield(screenshot) if block_given?
|
152
|
+
|
153
|
+
# logger.info 'Compressing screenshot...'
|
154
|
+
# compress_result = compress_screenshot64 screenshot, last_screenshot
|
155
|
+
# logger.info 'Done! Getting title...'
|
156
|
+
a_title = title
|
157
|
+
# logger.info 'Done!'
|
158
|
+
Applitools::AppOutputWithScreenshot.new(
|
159
|
+
Applitools::AppOutput.new(a_title, '').tap do |o|
|
160
|
+
o.location = region.location unless region.empty?
|
161
|
+
# o.dom_url = dom_url
|
162
|
+
o.screenshot_url = screenshot_url if respond_to?(:screenshot_url) && !screenshot_url.nil?
|
163
|
+
end,
|
164
|
+
nil,
|
165
|
+
true
|
166
|
+
)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'applitools/selenium/concerns/browser_types'
|
2
|
+
|
3
|
+
module Applitools
|
4
|
+
module Selenium
|
5
|
+
class RenderBrowserInfo < ::Applitools::AbstractConfiguration
|
6
|
+
DEFAULT_CONFIG = proc do
|
7
|
+
{
|
8
|
+
platform: 'linux',
|
9
|
+
browser_type: Applitools::Selenium::Concerns::BrowserTypes::CHROME,
|
10
|
+
size_mode: 'full-page',
|
11
|
+
viewport_size: Applitools::RectangleSize.from_any_argument(width: 0, height: 0)
|
12
|
+
}
|
13
|
+
end
|
14
|
+
class << self
|
15
|
+
def default_config
|
16
|
+
DEFAULT_CONFIG.call
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
object_field :viewport_size, Applitools::RectangleSize
|
21
|
+
enum_field :browser_type, Applitools::Selenium::Concerns::BrowserTypes.enum_values
|
22
|
+
string_field :platform
|
23
|
+
string_field :size_mode
|
24
|
+
string_field :baseline_env_name
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
"#{viewport_size} (#{browser_type})"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'oj'
|
2
|
+
module Applitools
|
3
|
+
module Selenium
|
4
|
+
class RenderRequest
|
5
|
+
include Applitools::Jsonable
|
6
|
+
json_fields :renderId, :webhook, :url, :dom, :resources, :scriptHooks,
|
7
|
+
:selectorsToFindRegionsFor, :send_dom
|
8
|
+
|
9
|
+
json_fields :renderInfo, :browser
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
#String webHook, String url, RGridDom dom, Map<String, RGridResource> resources, RenderInfo renderInfo, String platform, String browserName, Object scriptHooks, String[] selectorsToFindRegionsFor, boolean sendDom, Task task
|
13
|
+
options = Applitools::Utils.extract_options! args
|
14
|
+
self.script_hooks = {}
|
15
|
+
self.selectors_to_find_regions_for = []
|
16
|
+
options.keys.each do |k|
|
17
|
+
send("#{k}=", options[k]) if options[k] && respond_to?("#{k}=")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'applitools/selenium/visual_grid/vg_task'
|
3
|
+
|
4
|
+
module Applitools
|
5
|
+
module Selenium
|
6
|
+
class RenderTask < VGTask
|
7
|
+
include Applitools::Jsonable
|
8
|
+
MAX_FAILS_COUNT = 5
|
9
|
+
MAX_ITERATIONS = 100
|
10
|
+
|
11
|
+
attr_accessor :script, :running_test, :all_blobs, :resource_urls, :resource_cache, :put_cache, :server_connector,
|
12
|
+
:rendering_info, :request_resources, :dom_url_mod, :result
|
13
|
+
def initialize(name, script, running_test, resource_cache, put_cache, rendering_info, server_connector, mod = nil)
|
14
|
+
self.result = nil
|
15
|
+
self.script = script
|
16
|
+
self.running_test = running_test
|
17
|
+
self.resource_cache = resource_cache
|
18
|
+
self.put_cache = put_cache
|
19
|
+
self.server_connector = server_connector
|
20
|
+
self.rendering_info = rendering_info
|
21
|
+
self.dom_url_mod = mod
|
22
|
+
super(name) do
|
23
|
+
perform
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform
|
28
|
+
requests = Applitools::Selenium::RenderRequests.new
|
29
|
+
rq = prepare_data_for_rg(script_data)
|
30
|
+
requests << rq
|
31
|
+
fetch_fails = 0
|
32
|
+
# still_running = false
|
33
|
+
# need_more_dom = false
|
34
|
+
begin
|
35
|
+
response = nil
|
36
|
+
begin
|
37
|
+
response = server_connector.render(rendering_info['serviceUrl'], rendering_info['accessToken'], requests)
|
38
|
+
# rescue StandardError => _e
|
39
|
+
# response = server_connector.render(rendering_info['serviceUrl'], rendering_info['accessToken'], requests)
|
40
|
+
rescue StandardError => e
|
41
|
+
fetch_fails += 1
|
42
|
+
sleep 2
|
43
|
+
end
|
44
|
+
next unless response
|
45
|
+
running_render = response.first
|
46
|
+
rq.render_id = running_render['renderId']
|
47
|
+
need_more_dom = running_render['needMoreDom']
|
48
|
+
need_more_resources = running_render['renderStatus'] == 'need-more-resources'
|
49
|
+
still_running = need_more_resources || need_more_dom || fetch_fails > MAX_FAILS_COUNT
|
50
|
+
|
51
|
+
dom_resource = rq.dom.resource
|
52
|
+
|
53
|
+
cache_key = URI(dom_resource.url)
|
54
|
+
cache_key.query = "modifier=#{dom_url_mod}" if dom_url_mod
|
55
|
+
|
56
|
+
running_render['needMoreResources'].each do |resource_url|
|
57
|
+
put_cache.fetch_and_store(URI(resource_url)) do |_s|
|
58
|
+
server_connector.render_put_resource(
|
59
|
+
rendering_info['serviceUrl'],
|
60
|
+
rendering_info['accessToken'],
|
61
|
+
request_resources[URI(resource_url)],
|
62
|
+
running_render
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end if need_more_resources
|
66
|
+
|
67
|
+
if need_more_dom
|
68
|
+
put_cache.fetch_and_store(cache_key) do |_s|
|
69
|
+
server_connector.render_put_resource(
|
70
|
+
rendering_info['serviceUrl'],
|
71
|
+
rendering_info['accessToken'],
|
72
|
+
dom_resource,
|
73
|
+
running_render
|
74
|
+
)
|
75
|
+
end
|
76
|
+
put_cache[cache_key]
|
77
|
+
end
|
78
|
+
|
79
|
+
end while(still_running)
|
80
|
+
statuses = poll_render_status(rq)
|
81
|
+
raise Applitools::EyesError, "Render failed for #{statuses.first['renderId']} with the message: " \
|
82
|
+
"#{statuses.first['error']}" if statuses.first['status'] == 'error'
|
83
|
+
self.result = statuses.first
|
84
|
+
statuses
|
85
|
+
end
|
86
|
+
|
87
|
+
def poll_render_status(rq)
|
88
|
+
iterations = 0
|
89
|
+
statuses = []
|
90
|
+
begin
|
91
|
+
fails_count = 0
|
92
|
+
proc = proc do
|
93
|
+
server_connector.render_status_by_id(
|
94
|
+
rendering_info['serviceUrl'],
|
95
|
+
rendering_info['accessToken'],
|
96
|
+
Oj.dump(json_value([rq.render_id]))
|
97
|
+
)
|
98
|
+
end
|
99
|
+
begin
|
100
|
+
statuses = proc.call
|
101
|
+
fails_count = 0
|
102
|
+
rescue StandardError => _e
|
103
|
+
sleep 1
|
104
|
+
fails_count = fails_count + 1
|
105
|
+
ensure
|
106
|
+
iterations+=1
|
107
|
+
sleep 0.5
|
108
|
+
end while(fails_count > 0 and fails_count < 3)
|
109
|
+
finished = statuses.first.is_a?(Hash) && (statuses.first['status'] == 'error' || statuses.first['status'] == 'rendered' || iterations > MAX_ITERATIONS || false)
|
110
|
+
end while(!finished)
|
111
|
+
statuses
|
112
|
+
end
|
113
|
+
|
114
|
+
def script_data
|
115
|
+
@script_data ||= Oj.load script
|
116
|
+
end
|
117
|
+
|
118
|
+
def prepare_data_for_rg(data)
|
119
|
+
self.all_blobs = data["blobs"]
|
120
|
+
self.resource_urls = data["resourceUrls"]
|
121
|
+
self.request_resources = Applitools::Selenium::Concerns::RenderResources.new
|
122
|
+
# self.request_resources = {}
|
123
|
+
all_blobs.map {|blob| Applitools::Selenium::VGResource.parse_blob_from_script(blob)}.each do |blob|
|
124
|
+
request_resources[blob.url] = blob
|
125
|
+
end
|
126
|
+
resource_urls.each do |url|
|
127
|
+
resource_cache.fetch_and_store(URI(url)) do |s|
|
128
|
+
resp_proc = proc { |u| server_connector.download_resource(u) }
|
129
|
+
retry_count = 3
|
130
|
+
begin
|
131
|
+
retry_count -= 1
|
132
|
+
response = resp_proc.call(url)
|
133
|
+
end while response.status != 200 && retry_count > 0
|
134
|
+
s.synchronize do
|
135
|
+
Applitools::Selenium::VGResource.parse_response(url, response)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
resource_urls.each do |u|
|
141
|
+
key = URI(u)
|
142
|
+
request_resources[key] = resource_cache[key]
|
143
|
+
end
|
144
|
+
|
145
|
+
r_info = Applitools::Selenium::RenderInfo.new.tap do |r|
|
146
|
+
r.width = running_test.browser_info.viewport_size.width
|
147
|
+
r.height = running_test.browser_info.viewport_size.height
|
148
|
+
r.size_mode = running_test.browser_info.size_mode
|
149
|
+
end
|
150
|
+
|
151
|
+
dom = Applitools::Selenium::Concerns::RGridDom.new(
|
152
|
+
url: script_data["url"], dom_nodes: script_data['cdt'], resources: request_resources
|
153
|
+
)
|
154
|
+
|
155
|
+
Applitools::Selenium::RenderRequest.new(
|
156
|
+
webhook: rendering_info["resultsUrl"],
|
157
|
+
url: script_data["url"],
|
158
|
+
dom: dom,
|
159
|
+
resources: request_resources,
|
160
|
+
render_info: r_info,
|
161
|
+
browser: {name: running_test.browser_info.browser_type, platform: running_test.browser_info.platform},
|
162
|
+
script_hooks: nil,
|
163
|
+
selectors_to_find_region_for: nil,
|
164
|
+
send_dom: running_test.eyes.config.send_dom,
|
165
|
+
task: nil
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'thread'
|
2
|
+
module Applitools
|
3
|
+
module Selenium
|
4
|
+
class ResourceCache
|
5
|
+
attr_accessor :cache_map, :semaphore
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.cache_map = {}
|
9
|
+
self.semaphore = Mutex.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def contains?(url)
|
13
|
+
semaphore.synchronize do
|
14
|
+
cache_map.keys.include?(url) && !cache_map[url].nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
current_value = semaphore.synchronize do
|
20
|
+
cache_map[key]
|
21
|
+
end
|
22
|
+
return current_value unless cache_map[key].is_a? Applitools::Future
|
23
|
+
update_cache_map(key, cache_map[key].get)
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
Applitools::ArgumentGuard.is_a?(key, 'key', URI)
|
28
|
+
Applitools::ArgumentGuard.one_of?(value, 'value', [Applitools::Future, Applitools::Selenium::VGResource])
|
29
|
+
update_cache_map(key, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch_and_store(key, &block)
|
33
|
+
return self[key] if self.contains? key
|
34
|
+
return unless block_given?
|
35
|
+
self[key] = Applitools::Future.new(semaphore) do |semaphore|
|
36
|
+
block.call(semaphore)
|
37
|
+
end
|
38
|
+
return true if cache_map[key].is_a? Applitools::Future
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def update_cache_map(key, value)
|
45
|
+
semaphore.synchronize do
|
46
|
+
cache_map[key] = value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|