eyes_selenium 3.15.29 → 3.15.30

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