eyes_selenium 3.15.29 → 3.15.30

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78bb86756a687165700f5eb9941acf74a90605715ba75417b242b390d07c9167
4
- data.tar.gz: 9dea01979ae55abbb299119b78605ff7ddafb719e5319302a8f9f0805b9ec522
3
+ metadata.gz: f0a153b343d111f258a0628e29043634a0a000a6b28a0f46e338f2ee61d315dc
4
+ data.tar.gz: 056e78da5023f2f69d3056484b5b06c5c710bcaab45fce6c6af28af81cce1e4b
5
5
  SHA512:
6
- metadata.gz: f3276fd649a1e5683696dbcfe2015b908420eeb3a3a42e0a511fad412c24e2ba7834f1189de45da11852d5a7c557e249937cba2ba8e2085dc1125915cf91d8ea
7
- data.tar.gz: 2221ccfdda82b957cb838c1cd1127c2e88943099ff821812e9b7ca557e682cc8a59a7445ce9b6b9a2b4c0b34337d345051a2e926a8773af93154330da2f1084b
6
+ metadata.gz: dc23f5c1c1443b7a5d0c1b1296e9d75fdf0a0766ded52eff8321bfbabe224ba1215755a4d76b4020526233c75ec7fcd5aa9433669257111b9171a6c32e8cd002
7
+ data.tar.gz: 41131e9e97acf4848ee15a07aae65cc38c7f86ddc5b94b6054f5086a9f77280bad76ac8c66aba2105f321b9680724d1dd93e8bd174154693ecac31c2bb2f37df
@@ -0,0 +1,69 @@
1
+ require 'crass'
2
+
3
+ module Applitools
4
+ module Selenium
5
+ module CssParser
6
+ class FindEmbeddedResources
7
+ class << self
8
+ end
9
+
10
+ class CssParseError < Applitools::EyesError; end
11
+
12
+ attr_accessor :css
13
+
14
+ def initialize(css)
15
+ self.css = css
16
+ end
17
+
18
+ def imported_css
19
+ fetch_urls(import_rules)
20
+ end
21
+
22
+ def fonts
23
+ fetch_urls(font_face_rules)
24
+ end
25
+
26
+ def images
27
+ fetch_urls(images_rules)
28
+ end
29
+
30
+ private
31
+
32
+ def url(node)
33
+ url = node[:tokens].select { |t| t[:node] == :url }.first
34
+ return url[:value] if url && !url.empty?
35
+ url = node[:tokens].select { |t| t[:node] == :function && t[:value] == 'url' }.first
36
+ url_index = node[:tokens].index(url)
37
+ url_string_node = url_index && node[:tokens][url_index + 1]
38
+ url_string_node && url_string_node[:node] == :string && !url_string_node[:value].empty? && url_string_node[:value]
39
+ end
40
+
41
+ def fetch_urls(nodes)
42
+ nodes.map { |n| url(n) }.compact
43
+ end
44
+
45
+ def import_rules
46
+ css_nodes.select { |n| n[:node] == :at_rule && n[:name] == 'import' }
47
+ end
48
+
49
+ def font_face_rules
50
+ css_nodes.select { |n| n[:node] == :at_rule && n[:name] == 'font-face' }
51
+ end
52
+
53
+ def images_rules
54
+ css_nodes.select { |n| n[:node] == :style_rule }.map { |n| n[:children] }
55
+ .flatten
56
+ .select { |n| n[:node] == :property && (n[:name] == 'background' || n[:name] == 'background-image') }
57
+ end
58
+
59
+ def css_nodes
60
+ @css_nodes ||= parse_nodes
61
+ end
62
+
63
+ def parse_nodes
64
+ Crass.parse css
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,113 +1,167 @@
1
- # frozen_string_literal: false
2
-
3
- require 'benchmark'
4
- require 'timeout'
5
-
6
- require 'css_parser'
7
- include Benchmark
8
- module Applitools::Selenium
9
- module DomCapture
10
- CSS_DOWNLOAD_TIMEOUT = 2 # 2 seconds
11
- DOM_CAPTURE_TIMEOUT = 10 # 10 seconds
12
-
13
- extend self
14
-
15
- def get_window_dom(driver, logger)
16
- args_obj = {
17
- 'styleProps' => %w(
18
- background-color background-image background-size color border-width
19
- border-color border-style padding margin
20
- ),
21
- 'attributeProps' => nil,
22
- 'rectProps' => %w(width height top left),
23
- 'ignoredTagNames' => %w(HEAD SCRIPT)
24
- }
25
- dom_tree = ''
26
- if Timeout.respond_to?(:timeout)
27
- Timeout.timeout(DOM_CAPTURE_TIMEOUT) do
28
- dom_tree = driver.execute_script(Applitools::Selenium::DomCapture::DOM_CAPTURE_SCRIPT, args_obj)
29
- get_frame_dom(driver, { 'childNodes' => [dom_tree], 'tagName' => 'OUTER_HTML' }, logger)
30
- end
31
- else
32
- timeout(DOM_CAPTURE_TIMEOUT) do
33
- dom_tree = driver.execute_script(Applitools::Selenium::DomCapture::DOM_CAPTURE_SCRIPT, args_obj)
34
- get_frame_dom(driver, { 'childNodes' => [dom_tree], 'tagName' => 'OUTER_HTML' }, logger)
1
+ module Applitools
2
+ module Selenium
3
+ module DomCapture
4
+ extend self
5
+ DOM_EXTRACTION_TIMEOUT = 300 #seconds
6
+ def full_window_dom(driver, server_connector, logger, position_provider = nil)
7
+ return get_dom(driver, server_connector, logger) unless position_provider
8
+ scroll_top_and_return_back(position_provider) do
9
+ get_dom(driver, server_connector, logger)
35
10
  end
36
11
  end
37
- dom_tree
38
- end
39
12
 
40
- private
41
-
42
- def get_frame_dom(driver, dom_tree, logger)
43
- tag_name = dom_tree['tagName']
44
- return unless tag_name
45
- frame_index = 0
46
- loop(driver, dom_tree, logger) do |dom_sub_tree|
47
- # this block is called if IFRAME found
48
- driver.switch_to.frame(index: frame_index)
49
- get_frame_dom(driver, dom_sub_tree, logger)
50
- driver.switch_to.parent_frame
51
- frame_index += 1
13
+ def get_dom(driver, server_connector, logger)
14
+ original_frame_chain = driver.frame_chain
15
+ dom = get_frame_dom(driver, server_connector, logger)
16
+ unless original_frame_chain.empty?
17
+ driver.switch_to.default_content
18
+ driver.switch_to.frames(frame_chain: original_frame_chain)
19
+ end
20
+ # CSS processing
21
+
22
+ dom
52
23
  end
53
- end
54
24
 
55
- def loop(driver, dom_tree, logger)
56
- child_nodes = dom_tree['childNodes']
57
- return unless child_nodes
58
- iterate_child_nodes = proc do |node_childs|
59
- node_childs.each do |node|
60
- if node['tagName'].casecmp('IFRAME') == 0
61
- yield(node) if block_given?
25
+ def get_frame_dom(driver, server_connector, logger)
26
+ logger.info 'Trying to get DOM from driver'
27
+ start_time = Time.now
28
+ script_response = nil
29
+ loop do
30
+ result_as_string = driver.execute_script(CAPTURE_FRAME_SCRIPT + ' return __captureDomAndPoll();')
31
+ script_response = Oj.load(result_as_string)
32
+ status = script_response['status']
33
+ break if status == 'SUCCESS'
34
+ raise Applitools::EyesError, 'DOM extraction timeout!' if Time.now - start_time > DOM_EXTRACTION_TIMEOUT
35
+ raise Applitools::EyesError, "DOM extraction error: #{script_response['error']}" if script_response['error']
36
+ sleep(0.2)
37
+ end
38
+ response_lines = script_response['value'].split /\r?\n/
39
+ separators = Oj.load(response_lines.shift)
40
+ missing_css_list = []
41
+ missing_frame_list = []
42
+ data = []
43
+
44
+ puts separators
45
+
46
+ blocks = DomParts.new(missing_css_list, missing_frame_list, data)
47
+ collector = blocks.collectors.next
48
+ response_lines.each do |line|
49
+ if line == separators['separator']
50
+ collector = blocks.collectors.next
62
51
  else
63
- node['css'] = get_frame_bundled_css(driver, logger) if node['tagName'].casecmp('HTML') == 0
64
- iterate_child_nodes.call(node['childNodes']) unless node['childNodes'].nil?
52
+ collector << line
65
53
  end
66
54
  end
55
+ logger.info "Missing CSS: #{missing_css_list.count}"
56
+ logger.info "Missing frames: #{missing_frame_list.count}"
57
+ #fetch_css_files(missing_css_list)
58
+
59
+ frame_data = recurse_frames(driver, server_connector, logger, missing_frame_list)
60
+ result = replace(separators['iframeStartToken'], separators['iframeEndToken'], data.first, frame_data)
61
+ css_data = fetch_css_files(missing_css_list, server_connector)
62
+ replace(separators['cssStartToken'], separators['cssEndToken'], result, css_data)
63
+ rescue StandardError
64
+ logger.error(e.class)
65
+ logger.error(e.message)
66
+ logger.error(e)
67
+ return ''
67
68
  end
68
- iterate_child_nodes.call(child_nodes)
69
- end
70
69
 
71
- def get_frame_bundled_css(driver, logger)
72
- base_url = URI.parse(driver.current_url)
73
- parser = CssParser::Parser.new import: true, absolute_paths: true
74
- css_threads = []
75
- css_items = []
76
- driver.execute_script(Applitools::Selenium::DomCapture::CSS_CAPTURE_SCRIPT).each_with_index do |item, i|
77
- if (v = item['text'])
78
- css_items[i] = [v]
79
- elsif (v = item['href'])
80
- target_url = URI.parse(v)
81
- url_to_load = target_url.absolute? ? target_url : base_url.merge(target_url)
82
- css_threads << Thread.new(url_to_load) do |url|
83
- if Timeout.respond_to?(:timeout)
84
- Timeout.timeout(CSS_DOWNLOAD_TIMEOUT) do
85
- css_string, = parser.send(:read_remote_file, url)
86
- css_items[i] = [css_string, { base_uri: url }]
87
- end
88
- else
89
- timeout(CSS_DOWNLOAD_TIMEOUT) do
90
- css_string, = parser.send(:read_remote_file, url)
91
- css_items[i] = [css_string, { base_uri: url }]
92
- end
70
+ def fetch_css_files(missing_css_list, server_connector)
71
+ result = {}
72
+ missing_css_list.each do |url|
73
+ next if url.empty?
74
+ next if /^blob:/ =~ url
75
+
76
+ begin
77
+ missing_css_response = server_connector.download_resource(url)
78
+ response_headers = missing_css_response.headers
79
+ raise Applitools::EyesError, "Wrong response header: #{response_headers['content-type']}" unless
80
+ %r{^text/css.*}i =~ response_headers['content-type']
81
+
82
+ css = missing_css_response.body
83
+
84
+ found_and_missing_css = Applitools::Selenium::CssParser::FindEmbeddedResources.new(css).imported_css.map do |found_url|
85
+ base_url(url).merge(found_url).to_s
93
86
  end
87
+ fetch_css_files(found_and_missing_css, server_connector).each do |_k, v|
88
+ css += v
89
+ end
90
+
91
+ result[url] = Oj.dump("\n/** #{url} **/\n" + css).gsub(/^\"|\"$/, '')
92
+ rescue StandardError
93
+ result[url] = ''
94
94
  end
95
95
  end
96
+ result
96
97
  end
97
- begin
98
- css_threads.each(&:join)
99
- rescue CssParser::CircularReferenceError => e
100
- logger.respond_to?(:error) && logger.error("Got a circular reference error! #{e.message}")
101
- rescue CssParser::RemoteFileError => e
102
- logger.respond_to?(:error) && logger.error("File download error - #{e.message}")
103
- rescue StandardError => e
104
- logger.respond_to?(:error) && logger.error("#{e.class} - #{e.message}")
98
+
99
+ def recurse_frames(driver, server_connector, logger, missing_frame_list)
100
+ return if missing_frame_list.empty?
101
+ frame_data = {}
102
+ frame_chain = driver.frame_chain
103
+ origin_location = driver.execute_script('return document.location.href')
104
+ missing_frame_list.each do |missing_frame_line|
105
+ logger.info "Switching to frame line: #{missing_frame_line}"
106
+ missing_frame_line.split(/,/).each do |xpath|
107
+ logger.info "switching to specific frame: #{xpath}"
108
+ frame_element = driver.find_element(:xpath, xpath)
109
+ frame_src = frame_element.attribute('src')
110
+ driver.switch_to.frame(frame_element)
111
+ logger.info "Switched to frame ( #{xpath} ) with src( #{frame_src} )"
112
+ end
113
+ location_after_switch = driver.execute_script('return document.location.href')
114
+
115
+ if origin_location == location_after_switch
116
+ logger.info "Switch to frame (#{missing_frame_line}) failed"
117
+ frame_data[missing_frame_line] = ''
118
+ else
119
+ result = get_frame_dom(driver, server_connector, logger)
120
+ frame_data[missing_frame_line] = result
121
+ end
122
+ end
123
+ driver.switch_to.default_content
124
+ driver.switch_to.frames(frame_chain: frame_chain)
125
+ frame_data
126
+ end
127
+
128
+ def scroll_top_and_return_back(position_provider)
129
+ original_position = position_provider.current_position
130
+ return yield if block_given? && original_position.nil?
131
+ position_provider.scroll_to Applitools::Location.new(0, 0)
132
+ result = yield if block_given?
133
+ position_provider.scroll_to original_position
134
+ result
135
+ end
136
+
137
+ def replace(open_token, close_token, input, replacements)
138
+ pattern = /#{open_token}(?<key>.+?)#{close_token}/
139
+ input.gsub(pattern) { |_m| replacements[Regexp.last_match(1)] }
105
140
  end
106
141
 
107
- css_items.each { |css| parser.add_block!(*css) if css && css[0] }
108
- css_result = ''
109
- parser.each_rule_set { |s| css_result.concat(s.to_s) }
110
- css_result
142
+ def base_url(url)
143
+ uri = URI.parse(url)
144
+ uri.query = uri.fragment = nil;
145
+ uri.path = ''
146
+ uri
147
+ end
148
+
149
+ class DomParts
150
+ attr_accessor :dom_part_collectors
151
+ def initialize(*args)
152
+ self.dom_part_collectors = args
153
+ @index = 0
154
+ end
155
+
156
+ def collectors
157
+ @collectors ||= Enumerator.new(dom_part_collectors.length) do |y|
158
+ loop do
159
+ y << dom_part_collectors[@index]
160
+ @index += 1
161
+ end
162
+ end
163
+ end
164
+ end
111
165
  end
112
166
  end
113
167
  end
@@ -1,103 +1,462 @@
1
- # frozen_string_literal: true
2
- #
3
- module Applitools::Selenium
4
- module DomCapture
5
- CSS_CAPTURE_SCRIPT = <<-SCRIPT
6
- function extractCssResources() {
7
- return Array.from(document.querySelectorAll('link[rel="stylesheet"],style')).map(el => {
8
- if (el.tagName.toUpperCase() === 'LINK') {
9
- return {"href": el.getAttribute('href')};
10
- } else {
11
- return {"text": el.textContent};
1
+ module Applitools
2
+ module Selenium
3
+ module DomCapture
4
+ CAPTURE_FRAME_SCRIPT = <<'SCRIPT'
5
+ /* @applitools/dom-capture@7.0.14 */
6
+
7
+ function __captureDomAndPoll() {
8
+ var captureDomAndPoll = (function () {
9
+ 'use strict';
10
+
11
+ const styleProps = [
12
+ 'background-repeat',
13
+ 'background-origin',
14
+ 'background-position',
15
+ 'background-color',
16
+ 'background-image',
17
+ 'background-size',
18
+ 'border-width',
19
+ 'border-color',
20
+ 'border-style',
21
+ 'color',
22
+ 'display',
23
+ 'font-size',
24
+ 'line-height',
25
+ 'margin',
26
+ 'opacity',
27
+ 'overflow',
28
+ 'padding',
29
+ 'visibility',
30
+ ];
31
+
32
+ const rectProps = ['width', 'height', 'top', 'left'];
33
+
34
+ const ignoredTagNames = ['HEAD', 'SCRIPT'];
35
+
36
+ var defaultDomProps = {
37
+ styleProps,
38
+ rectProps,
39
+ ignoredTagNames,
40
+ };
41
+
42
+ const bgImageRe = /url\((?!['"]?:)['"]?([^'")]*)['"]?\)/;
43
+
44
+ function getBackgroundImageUrl(cssText) {
45
+ const match = cssText ? cssText.match(bgImageRe) : undefined;
46
+ return match ? match[1] : match;
47
+ }
48
+
49
+ var getBackgroundImageUrl_1 = getBackgroundImageUrl;
50
+
51
+ const psetTimeout = t =>
52
+ new Promise(res => {
53
+ setTimeout(res, t);
54
+ });
55
+
56
+ async function getImageSizes({bgImages, timeout = 5000, Image = window.Image}) {
57
+ return (await Promise.all(
58
+ Array.from(bgImages).map(url =>
59
+ Promise.race([
60
+ new Promise(resolve => {
61
+ const img = new Image();
62
+ img.onload = () => resolve({url, width: img.naturalWidth, height: img.naturalHeight});
63
+ img.onerror = () => resolve();
64
+ img.src = url;
65
+ }),
66
+ psetTimeout(timeout),
67
+ ]),
68
+ ),
69
+ )).reduce((images, curr) => {
70
+ if (curr) {
71
+ images[curr.url] = {width: curr.width, height: curr.height};
72
+ }
73
+ return images;
74
+ }, {});
75
+ }
76
+
77
+ var getImageSizes_1 = getImageSizes;
78
+
79
+ function genXpath(el) {
80
+ if (!el.ownerDocument) return ''; // this is the document node
81
+
82
+ let xpath = '',
83
+ currEl = el,
84
+ doc = el.ownerDocument,
85
+ frameElement = doc.defaultView.frameElement;
86
+ while (currEl !== doc) {
87
+ xpath = `${currEl.tagName}[${getIndex(currEl)}]/${xpath}`;
88
+ currEl = currEl.parentNode;
89
+ }
90
+ if (frameElement) {
91
+ xpath = `${genXpath(frameElement)},${xpath}`;
92
+ }
93
+ return xpath.replace(/\/$/, '');
94
+ }
95
+
96
+ function getIndex(el) {
97
+ return (
98
+ Array.prototype.filter
99
+ .call(el.parentNode.childNodes, node => node.tagName === el.tagName)
100
+ .indexOf(el) + 1
101
+ );
102
+ }
103
+
104
+ var genXpath_1 = genXpath;
105
+
106
+ function absolutizeUrl(url, absoluteUrl) {
107
+ return new URL(url, absoluteUrl).href;
108
+ }
109
+
110
+ var absolutizeUrl_1 = absolutizeUrl;
111
+
112
+ function makeGetBundledCssFromCssText({
113
+ parseCss,
114
+ CSSImportRule,
115
+ absolutizeUrl,
116
+ fetchCss,
117
+ unfetchedToken,
118
+ }) {
119
+ return async function getBundledCssFromCssText(cssText, resourceUrl) {
120
+ let unfetchedResources;
121
+ let bundledCss = '';
122
+
123
+ try {
124
+ const styleSheet = parseCss(cssText);
125
+ for (const rule of Array.from(styleSheet.cssRules)) {
126
+ if (rule instanceof CSSImportRule) {
127
+ const nestedUrl = absolutizeUrl(rule.href, resourceUrl);
128
+ const nestedResource = await fetchCss(nestedUrl);
129
+ if (nestedResource !== undefined) {
130
+ const {
131
+ bundledCss: nestedCssText,
132
+ unfetchedResources: nestedUnfetchedResources,
133
+ } = await getBundledCssFromCssText(nestedResource, nestedUrl);
134
+
135
+ nestedUnfetchedResources && (unfetchedResources = new Set(nestedUnfetchedResources));
136
+ bundledCss = `${nestedCssText}${bundledCss}`;
137
+ } else {
138
+ unfetchedResources = new Set([nestedUrl]);
139
+ bundledCss = `\n${unfetchedToken}${nestedUrl}${unfetchedToken}`;
140
+ }
12
141
  }
13
- });
142
+ }
143
+ } catch (ex) {
144
+ console.log(`error during getBundledCssFromCssText, resourceUrl=${resourceUrl}`, ex);
14
145
  }
15
- return extractCssResources();
16
- SCRIPT
17
- DOM_CAPTURE_SCRIPT = <<-SCRIPT
18
- function captureFrame({ styleProps, attributeProps, rectProps, ignoredTagNames }) {
19
- const NODE_TYPES = {
20
- ELEMENT: 1,
21
- TEXT: 3,
22
- };
23
- function filter(x) {
24
- return !!x;
146
+
147
+ bundledCss = `${bundledCss}${getCss(cssText, resourceUrl)}`;
148
+
149
+ return {
150
+ bundledCss,
151
+ unfetchedResources,
152
+ };
153
+ };
154
+ }
155
+
156
+ function getCss(newText, url) {
157
+ return `\n/** ${url} **/\n${newText}`;
158
+ }
159
+
160
+ var getBundledCssFromCssText = makeGetBundledCssFromCssText;
161
+
162
+ function parseCss(styleContent) {
163
+ var doc = document.implementation.createHTMLDocument(''),
164
+ styleElement = doc.createElement('style');
165
+ styleElement.textContent = styleContent;
166
+ // the style will only be parsed once it is added to a document
167
+ doc.body.appendChild(styleElement);
168
+
169
+ return styleElement.sheet;
170
+ }
171
+
172
+ var parseCss_1 = parseCss;
173
+
174
+ function makeFetchCss(fetch) {
175
+ return async function fetchCss(url) {
176
+ try {
177
+ const response = await fetch(url, {cache: 'force-cache'});
178
+ if (response.ok) {
179
+ return await response.text();
25
180
  }
26
- function notEmptyObj(obj) {
27
- return Object.keys(obj).length ? obj : undefined;
181
+ console.log('/failed to fetch (status ' + response.status + ') css from: ' + url + '/');
182
+ } catch (err) {
183
+ console.log('/failed to fetch (error ' + err.toString() + ') css from: ' + url + '/');
184
+ }
185
+ };
186
+ }
187
+
188
+ var fetchCss = makeFetchCss;
189
+
190
+ function makeExtractCssFromNode({fetchCss, absolutizeUrl}) {
191
+ return async function extractCssFromNode(node, baseUrl) {
192
+ let cssText, resourceUrl, isUnfetched;
193
+ if (isStyleElement(node)) {
194
+ cssText = Array.from(node.childNodes)
195
+ .map(node => node.nodeValue)
196
+ .join('');
197
+ resourceUrl = baseUrl;
198
+ } else if (isLinkToStyleSheet(node)) {
199
+ resourceUrl = absolutizeUrl(getHrefAttr(node), baseUrl);
200
+ cssText = await fetchCss(resourceUrl);
201
+ if (cssText === undefined) {
202
+ isUnfetched = true;
28
203
  }
29
- function iframeToJSON(el) {
30
- const obj = elementToJSON(el);
31
- try {
32
- if (el.contentDocument) {
33
- obj.childNodes = [captureNode(el.contentDocument.documentElement)];
34
- }
35
- } catch (ex) {
36
- } finally {
37
- return obj;
204
+ }
205
+ return {cssText, resourceUrl, isUnfetched};
206
+ };
207
+ }
208
+
209
+ function isStyleElement(node) {
210
+ return node.nodeName && node.nodeName.toUpperCase() === 'STYLE';
211
+ }
212
+
213
+ function getHrefAttr(node) {
214
+ const attr = Array.from(node.attributes).find(attr => attr.name.toLowerCase() === 'href');
215
+ return attr && attr.value;
216
+ }
217
+
218
+ function isLinkToStyleSheet(node) {
219
+ return (
220
+ node.nodeName &&
221
+ node.nodeName.toUpperCase() === 'LINK' &&
222
+ node.attributes &&
223
+ Array.from(node.attributes).find(
224
+ attr => attr.name.toLowerCase() === 'rel' && attr.value.toLowerCase() === 'stylesheet',
225
+ )
226
+ );
227
+ }
228
+
229
+ var extractCssFromNode = makeExtractCssFromNode;
230
+
231
+ function makeCaptureNodeCss({extractCssFromNode, getBundledCssFromCssText, unfetchedToken}) {
232
+ return async function captureNodeCss(node, baseUrl) {
233
+ const {resourceUrl, cssText, isUnfetched} = await extractCssFromNode(node, baseUrl);
234
+
235
+ let unfetchedResources;
236
+ let bundledCss = '';
237
+ if (cssText) {
238
+ const {
239
+ bundledCss: nestedCss,
240
+ unfetchedResources: nestedUnfetched,
241
+ } = await getBundledCssFromCssText(cssText, resourceUrl);
242
+
243
+ bundledCss += nestedCss;
244
+ unfetchedResources = new Set(nestedUnfetched);
245
+ } else if (isUnfetched) {
246
+ bundledCss += `${unfetchedToken}${resourceUrl}${unfetchedToken}`;
247
+ unfetchedResources = new Set([resourceUrl]);
248
+ }
249
+ return {bundledCss, unfetchedResources};
250
+ };
251
+ }
252
+
253
+ var captureNodeCss = makeCaptureNodeCss;
254
+
255
+ const NODE_TYPES = {
256
+ ELEMENT: 1,
257
+ TEXT: 3,
258
+ };
259
+ const API_VERSION = '1.0.0';
260
+
261
+ async function captureFrame(
262
+ {styleProps, rectProps, ignoredTagNames} = defaultDomProps,
263
+ doc = document,
264
+ ) {
265
+ const start = Date.now();
266
+ const unfetchedResources = new Set();
267
+ const iframeCors = [];
268
+ const iframeToken = '@@@@@';
269
+ const unfetchedToken = '#####';
270
+ const separator = '-----';
271
+
272
+ const fetchCss$$1 = fetchCss(fetch);
273
+ const getBundledCssFromCssText$$1 = getBundledCssFromCssText({
274
+ parseCss: parseCss_1,
275
+ CSSImportRule,
276
+ fetchCss: fetchCss$$1,
277
+ absolutizeUrl: absolutizeUrl_1,
278
+ unfetchedToken,
279
+ });
280
+ const extractCssFromNode$$1 = extractCssFromNode({fetchCss: fetchCss$$1, absolutizeUrl: absolutizeUrl_1});
281
+ const captureNodeCss$$1 = captureNodeCss({
282
+ extractCssFromNode: extractCssFromNode$$1,
283
+ getBundledCssFromCssText: getBundledCssFromCssText$$1,
284
+ unfetchedToken,
285
+ });
286
+
287
+ // Note: Change the API_VERSION when changing json structure.
288
+ const capturedFrame = await doCaptureFrame(doc);
289
+ capturedFrame.version = API_VERSION;
290
+
291
+ const iframePrefix = iframeCors.length ? `${iframeCors.join('\n')}\n` : '';
292
+ const unfetchedPrefix = unfetchedResources.size
293
+ ? `${Array.from(unfetchedResources).join('\n')}\n`
294
+ : '';
295
+ const metaPrefix = JSON.stringify({
296
+ separator,
297
+ cssStartToken: unfetchedToken,
298
+ cssEndToken: unfetchedToken,
299
+ iframeStartToken: `"${iframeToken}`,
300
+ iframeEndToken: `${iframeToken}"`,
301
+ });
302
+ const ret = `${metaPrefix}\n${unfetchedPrefix}${separator}\n${iframePrefix}${separator}\n${JSON.stringify(
303
+ capturedFrame,
304
+ )}`;
305
+ console.log('[captureFrame]', Date.now() - start);
306
+ return ret;
307
+
308
+ function filter(x) {
309
+ return !!x;
310
+ }
311
+
312
+ function notEmptyObj(obj) {
313
+ return Object.keys(obj).length ? obj : undefined;
314
+ }
315
+
316
+ function captureTextNode(node) {
317
+ return {
318
+ tagName: '#text',
319
+ text: node.textContent,
320
+ };
321
+ }
322
+
323
+ async function doCaptureFrame(frameDoc) {
324
+ const bgImages = new Set();
325
+ let bundledCss = '';
326
+ const ret = await captureNode(frameDoc.documentElement);
327
+ ret.css = bundledCss;
328
+ ret.images = await getImageSizes_1({bgImages});
329
+ return ret;
330
+
331
+ async function captureNode(node) {
332
+ const {bundledCss: nodeCss, unfetchedResources: nodeUnfetched} = await captureNodeCss$$1(
333
+ node,
334
+ frameDoc.location.href,
335
+ );
336
+ bundledCss += nodeCss;
337
+ if (nodeUnfetched) for (const elem of nodeUnfetched) unfetchedResources.add(elem);
338
+
339
+ switch (node.nodeType) {
340
+ case NODE_TYPES.TEXT: {
341
+ return captureTextNode(node);
38
342
  }
39
- }
40
- function elementToJSON(el) {
41
- const tagName = el.tagName.toUpperCase();
42
- if (ignoredTagNames.indexOf(tagName) > -1) return null;
43
- const computedStyle = window.getComputedStyle(el);
44
- const boundingClientRect = el.getBoundingClientRect();
45
- const style = {};
46
- for (const p of styleProps) style[p] = computedStyle.getPropertyValue(p);
47
- const rect = {};
48
- for (const p of rectProps) rect[p] = boundingClientRect[p];
49
- const attributes = {};
50
- if (!attributeProps) {
51
- if (el.hasAttributes()) {
52
- var attrs = el.attributes;
53
- for (const p of attrs) {
54
- attributes[p.name] = p.value;
55
- }
343
+ case NODE_TYPES.ELEMENT: {
344
+ const tagName = node.tagName.toUpperCase();
345
+ if (tagName === 'IFRAME') {
346
+ return await iframeToJSON(node);
347
+ } else {
348
+ return await await elementToJSON(node);
56
349
  }
57
350
  }
58
- else {
59
- if (attributeProps.all) {
60
- for (const p of attributeProps.all) {
61
- if (el.hasAttribute(p)) attributes[p] = el.getAttribute(p);
62
- }
63
- }
64
- if (attributeProps[tagName]) {
65
- for (const p of attributeProps[tagName]) {
66
- if (el.hasAttribute(p)) attributes[p] = el.getAttribute(p);
67
- }
68
- }
351
+ default: {
352
+ return null;
69
353
  }
70
- return {
71
- tagName,
72
- style: notEmptyObj(style),
73
- rect: notEmptyObj(rect),
74
- attributes: notEmptyObj(attributes),
75
- childNodes: Array.prototype.map.call(el.childNodes, captureNode).filter(filter),
76
- };
77
354
  }
78
- function captureTextNode(node) {
79
- return {
80
- tagName: '#text',
81
- text: node.textContent,
82
- };
355
+ }
356
+
357
+ async function elementToJSON(el) {
358
+ const childNodes = (await Promise.all(
359
+ Array.prototype.map.call(el.childNodes, captureNode),
360
+ )).filter(filter);
361
+
362
+ const tagName = el.tagName.toUpperCase();
363
+ if (ignoredTagNames.indexOf(tagName) > -1) return null;
364
+
365
+ const computedStyle = window.getComputedStyle(el);
366
+ const boundingClientRect = el.getBoundingClientRect();
367
+
368
+ const style = {};
369
+ for (const p of styleProps) style[p] = computedStyle.getPropertyValue(p);
370
+
371
+ const rect = {};
372
+ for (const p of rectProps) rect[p] = boundingClientRect[p];
373
+
374
+ const attributes = Array.from(el.attributes)
375
+ .map(a => ({key: a.name, value: a.value}))
376
+ .reduce((obj, attr) => {
377
+ obj[attr.key] = attr.value;
378
+ return obj;
379
+ }, {});
380
+
381
+ const bgImage = getBackgroundImageUrl_1(computedStyle.getPropertyValue('background-image'));
382
+ if (bgImage) {
383
+ bgImages.add(bgImage);
384
+ }
385
+
386
+ return {
387
+ tagName,
388
+ style: notEmptyObj(style),
389
+ rect: notEmptyObj(rect),
390
+ attributes: notEmptyObj(attributes),
391
+ childNodes,
392
+ };
393
+ }
394
+
395
+ async function iframeToJSON(el) {
396
+ const obj = await elementToJSON(el);
397
+ let doc;
398
+ try {
399
+ doc = el.contentDocument;
400
+ } catch (ex) {
401
+ markFrameAsCors();
402
+ return obj;
83
403
  }
84
- function captureNode(node) {
85
- switch (node.nodeType) {
86
- case NODE_TYPES.TEXT:
87
- return captureTextNode(node);
88
- case NODE_TYPES.ELEMENT:
89
- if (node.tagName.toUpperCase() === 'IFRAME') {
90
- return iframeToJSON(node);
91
- } else {
92
- return elementToJSON(node);
93
- }
94
- default:
95
- return null;
404
+ try {
405
+ if (doc) {
406
+ obj.childNodes = [await doCaptureFrame(el.contentDocument)];
407
+ } else {
408
+ markFrameAsCors();
96
409
  }
410
+ } catch (ex) {
411
+ console.log('error in iframeToJSON', ex);
412
+ }
413
+ return obj;
414
+
415
+ function markFrameAsCors() {
416
+ const xpath = genXpath_1(el);
417
+ iframeCors.push(xpath);
418
+ obj.childNodes = [`${iframeToken}${xpath}${iframeToken}`];
97
419
  }
98
- return captureNode(document.documentElement);
99
420
  }
100
- return captureFrame(arguments[0]);
101
- SCRIPT
421
+ }
422
+ }
423
+
424
+ var captureFrame_1 = captureFrame;
425
+
426
+ const EYES_NAME_SPACE = '__EYES__APPLITOOLS__';
427
+
428
+ function captureFrameAndPoll(...args) {
429
+ if (!window[EYES_NAME_SPACE]) {
430
+ window[EYES_NAME_SPACE] = {};
431
+ }
432
+ if (!window[EYES_NAME_SPACE].captureDomResult) {
433
+ window[EYES_NAME_SPACE].captureDomResult = {
434
+ status: 'WIP',
435
+ value: null,
436
+ error: null,
437
+ };
438
+ captureFrame_1(...args)
439
+ .then(r => ((resultObject.status = 'SUCCESS'), (resultObject.value = r)))
440
+ .catch(e => ((resultObject.status = 'ERROR'), (resultObject.error = e.message)));
441
+ }
442
+
443
+ const resultObject = window[EYES_NAME_SPACE].captureDomResult;
444
+ if (resultObject.status === 'SUCCESS') {
445
+ window[EYES_NAME_SPACE].captureDomResult = null;
446
+ }
447
+
448
+ return JSON.stringify(resultObject);
449
+ }
450
+
451
+ var captureFrameAndPoll_1 = captureFrameAndPoll;
452
+
453
+ return captureFrameAndPoll_1;
454
+
455
+ }());
456
+
457
+ return captureDomAndPoll.apply(this, arguments);
458
+ }
459
+ SCRIPT
460
+ end
102
461
  end
103
- end
462
+ end