eyes_selenium 3.14.3 → 3.14.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/lib/applitools/selenium/border_aware_element_content_location_provider.rb +2 -0
  3. data/lib/applitools/selenium/browser.rb +66 -56
  4. data/lib/applitools/selenium/context_based_scale_provider.rb +2 -0
  5. data/lib/applitools/selenium/css_transform/css_transform.rb +21 -0
  6. data/lib/applitools/selenium/css_translate_element_position_provider.rb +48 -0
  7. data/lib/applitools/selenium/css_translate_position_provider.rb +29 -10
  8. data/lib/applitools/selenium/dom_capture/dom_capture.rb +113 -0
  9. data/lib/applitools/selenium/dom_capture/dom_capture_script.rb +103 -0
  10. data/lib/applitools/selenium/driver.rb +9 -2
  11. data/lib/applitools/selenium/element.rb +2 -0
  12. data/lib/applitools/selenium/element_position_provider.rb +3 -1
  13. data/lib/applitools/selenium/entire_element_screenshot.rb +20 -0
  14. data/lib/applitools/selenium/eyes.rb +203 -396
  15. data/lib/applitools/selenium/eyes_screenshot.rb +60 -0
  16. data/lib/applitools/selenium/eyes_target_locator.rb +12 -24
  17. data/lib/applitools/selenium/fixed_cut_provider.rb +48 -0
  18. data/lib/applitools/selenium/frame.rb +11 -1
  19. data/lib/applitools/selenium/frame_chain.rb +9 -1
  20. data/lib/applitools/selenium/full_page_capture_algorithm.rb +85 -50
  21. data/lib/applitools/selenium/fullpage_screenshot.rb +37 -0
  22. data/lib/applitools/selenium/keyboard.rb +2 -0
  23. data/lib/applitools/selenium/mouse.rb +2 -0
  24. data/lib/applitools/selenium/move_to_region_visibility_strategy.rb +2 -0
  25. data/lib/applitools/selenium/nop_region_visibility_strategy.rb +2 -0
  26. data/lib/applitools/selenium/region_provider.rb +91 -0
  27. data/lib/applitools/selenium/sauce/driver.rb +2 -0
  28. data/lib/applitools/selenium/scroll_position_provider.rb +13 -2
  29. data/lib/applitools/selenium/takes_screenshot_image_provider.rb +5 -3
  30. data/lib/applitools/selenium/target.rb +32 -6
  31. data/lib/applitools/selenium/viewport_screenshot.rb +46 -0
  32. data/lib/applitools/selenium/viewport_size.rb +2 -0
  33. data/lib/applitools/version.rb +3 -1
  34. data/lib/eyes_selenium.rb +4 -9
  35. metadata +16 -27
  36. data/lib/applitools/capybara.rb +0 -8
  37. data/lib/applitools/poltergeist/applitools_compatible.rb +0 -32
  38. data/lib/applitools/poltergeist/driver.rb +0 -11
  39. data/lib/applitools/selenium/capybara/capybara_settings.rb +0 -23
  40. data/lib/applitools/selenium/capybara/driver.rb +0 -37
  41. data/lib/applitools/selenium/eyes_full_page_screenshot.rb +0 -47
  42. data/lib/applitools/selenium/eyes_web_driver_screenshot.rb +0 -341
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a85a4fbc19f3a4c5fd8e57fbd839ae3d92cbbd76
4
- data.tar.gz: c345ecf5f123efdefe2ebb083b67f92ba0d6234d
2
+ SHA256:
3
+ metadata.gz: eed17b1ab4d0917d2cb7f0048435f2b8b5b5a41c29607bfc82caa0da521e071e
4
+ data.tar.gz: d8398e29b30ba60e8d033608d0e70193413866e5509c057206357840b58ca0e9
5
5
  SHA512:
6
- metadata.gz: 9f7877bb8325b148309618f5ee68c8bd5ea396eecdfdc2cd48a0d23fbff3549189793883dacc6c15a438c71f36389cc0ba08c68070d2035c21f6d184df74b303
7
- data.tar.gz: ef3229dc823585a9d62904d1c8bbac5fd90359826e61634811c84d9808eddff0eb84f34b6a551748ed44d6d41201d42e0cd7a3a26a5281a6ffe92334ddfef641
6
+ metadata.gz: 214c9dd3f2085254d46241980ca5a416a4b4f70cda46f54a65adf36b85198d53db267fb9d8275c5d98f1f9543e06343dcbe36938d80eab7c1b1fe2e4e013bc20
7
+ data.tar.gz: 824ea45b22311de40906cd2a2e19b8652d7eff6b778e6f43379acc7f600397e7d5ad0a49ee55909e29303d585241200c16771e976e346546dbe8c51f8e680064
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Applitools::Selenium
2
4
  # @!visibility private
3
5
  class BorderAwareElementContentLocationProvider
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: false
2
+
1
3
  module Applitools::Selenium
2
4
  # @!visibility private
3
5
  class Browser
@@ -69,7 +71,15 @@ module Applitools::Selenium
69
71
  end
70
72
 
71
73
  def chrome?
72
- @driver.browser == :chrome
74
+ running_browser_name == :chrome
75
+ end
76
+
77
+ def firefox?
78
+ running_browser_name == :firefox
79
+ end
80
+
81
+ def running_browser_name
82
+ @driver.__getobj__.browser
73
83
  end
74
84
 
75
85
  def user_agent
@@ -149,62 +159,62 @@ module Applitools::Selenium
149
159
  set_transform("translate(-#{point.left}px, -#{point.top}px)")
150
160
  end
151
161
 
152
- # Takes a full page screenshot.
162
+ # # Takes a full page screenshot.
163
+ # #
164
+ # # @return [ChunkyPNG::Canvas] image The result of the screenshot.
165
+ # def fullpage_screenshot
166
+ # # Scroll to the top/left corner of the screen.
167
+ # original_scroll_position = current_scroll_position
168
+ # scroll_to(Applitools::Base::Point::TOP_LEFT)
169
+ # if current_scroll_position != Applitools::Base::Point::TOP_LEFT
170
+ # raise 'Could not scroll to the top/left corner of the screen!'
171
+ # end
153
172
  #
154
- # @return [ChunkyPNG::Canvas] image The result of the screenshot.
155
- def fullpage_screenshot
156
- # Scroll to the top/left corner of the screen.
157
- original_scroll_position = current_scroll_position
158
- scroll_to(Applitools::Base::Point::TOP_LEFT)
159
- if current_scroll_position != Applitools::Base::Point::TOP_LEFT
160
- raise 'Could not scroll to the top/left corner of the screen!'
161
- end
162
-
163
- # Translate to top/left of the page (notice this is different from JavaScript scrolling).
164
- if @eyes.use_css_transition
165
- original_transform = current_transform
166
- translate_to(Applitools::Base::Point::TOP_LEFT)
167
- end
168
-
169
- # Take screenshot of the (0,0) tile.
170
- screenshot = @driver.visible_screenshot
171
-
172
- # Normalize screenshot width/height.
173
- size_factor = 1
174
- page_size = entire_page_size
175
- factor = image_normalization_factor(screenshot)
176
- if factor == 0.5
177
- size_factor = 2
178
- page_size.width *= size_factor
179
- page_size.height *= size_factor
180
- page_size.width = [page_size.width, screenshot.width].max
181
- end
182
-
183
- # NOTE: this is required! Since when calculating the screenshot parts for full size, we use a screenshot size
184
- # which is a bit smaller (see comment below).
185
- if screenshot.width < page_size.width || screenshot.height < page_size.height
186
- # We use a smaller size than the actual screenshot size in order to eliminate duplication of bottom scroll bars,
187
- # as well as footer-like elements with fixed position.
188
- max_scrollbar_size = @eyes.use_css_transition ? 0 : MAX_SCROLLBAR_SIZE
189
- height = [screenshot.height - (max_scrollbar_size * size_factor), MIN_SCREENSHOT_PART_HEIGHT * size_factor].max
190
- screenshot_part_size = Applitools::Base::Dimension.new(screenshot.width, height)
191
-
192
- sub_regions = Applitools::Base::Region.new(0, 0, page_size.width,
193
- page_size.height).subregions(screenshot_part_size)
194
- parts = sub_regions.map do |screenshot_part|
195
- # Skip (0,0), as we already got the screenshot.
196
- if screenshot_part.left.zero? && screenshot_part.top.zero?
197
- next Applitools::Base::ImagePosition.new(screenshot, Applitools::Base::Point::TOP_LEFT)
198
- end
199
-
200
- process_screenshot_part(screenshot_part, size_factor)
201
- end
202
- screenshot = Applitools::Utils::ImageUtils.stitch_images(page_size, parts)
203
- end
204
- set_transform(original_transform) if @eyes.use_css_transition
205
- scroll_to(original_scroll_position)
206
- screenshot
207
- end
173
+ # # Translate to top/left of the page (notice this is different from JavaScript scrolling).
174
+ # if @eyes.use_css_transition
175
+ # original_transform = current_transform
176
+ # translate_to(Applitools::Base::Point::TOP_LEFT)
177
+ # end
178
+ #
179
+ # # Take screenshot of the (0,0) tile.
180
+ # screenshot = @driver.visible_screenshot
181
+ #
182
+ # # Normalize screenshot width/height.
183
+ # size_factor = 1
184
+ # page_size = entire_page_size
185
+ # factor = image_normalization_factor(screenshot)
186
+ # if factor == 0.5
187
+ # size_factor = 2
188
+ # page_size.width *= size_factor
189
+ # page_size.height *= size_factor
190
+ # page_size.width = [page_size.width, screenshot.width].max
191
+ # end
192
+ #
193
+ # # NOTE: this is required! Since when calculating the screenshot parts for full size, we use a screenshot size
194
+ # # which is a bit smaller (see comment below).
195
+ # if screenshot.width < page_size.width || screenshot.height < page_size.height
196
+ # # We use a smaller size than the actual screenshot size in order to eliminate duplication of bottom scroll bars,
197
+ # # as well as footer-like elements with fixed position.
198
+ # max_scrollbar_size = @eyes.use_css_transition ? 0 : MAX_SCROLLBAR_SIZE
199
+ # height = [screenshot.height - (max_scrollbar_size * size_factor), MIN_SCREENSHOT_PART_HEIGHT * size_factor].max
200
+ # screenshot_part_size = Applitools::Base::Dimension.new(screenshot.width, height)
201
+ #
202
+ # sub_regions = Applitools::Base::Region.new(0, 0, page_size.width,
203
+ # page_size.height).subregions(screenshot_part_size)
204
+ # parts = sub_regions.map do |screenshot_part|
205
+ # # Skip (0,0), as we already got the screenshot.
206
+ # if screenshot_part.left.zero? && screenshot_part.top.zero?
207
+ # next Applitools::Base::ImagePosition.new(screenshot, Applitools::Base::Point::TOP_LEFT)
208
+ # end
209
+ #
210
+ # process_screenshot_part(screenshot_part, size_factor)
211
+ # end
212
+ # screenshot = Applitools::Utils::ImageUtils.stitch_images(page_size, parts)
213
+ # end
214
+ # set_transform(original_transform) if @eyes.use_css_transition
215
+ # scroll_to(original_scroll_position)
216
+ # screenshot
217
+ # end
208
218
 
209
219
  private
210
220
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Applitools::Selenium
2
4
  # @!visibility private
3
5
  class ContextBasedScaleProvider
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Applitools
4
+ module CssTransform
5
+ private
6
+
7
+ def get_position_from_transform(transform)
8
+ regexp = /^translate\(\s*(\-?)([\d, \.]+)px,\s*(\-?)([\d, \.]+)px\s*\)/
9
+ data = regexp.match(transform)
10
+
11
+ raise Applitools::EyesError.new "Can't parse CSS transition: #{transform}!" unless data
12
+ x = data[2].to_f.round
13
+ y = data[4].to_f.round
14
+
15
+ x *= -1 unless data[1].empty?
16
+ y *= -1 unless data[3].empty?
17
+
18
+ Applitools::Location.new(x, y)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'element_position_provider'
4
+ require_relative 'css_transform/css_transform'
5
+
6
+ module Applitools::Selenium
7
+ # @!visibility private
8
+ class CssTranslateElementPositionProvider < ElementPositionProvider
9
+ include Applitools::CssTransform
10
+
11
+ # Gets the current position.
12
+ #
13
+ # @return [Applitools::Location] The location.
14
+ def current_position
15
+ position = super.offset_negative transforms_offset
16
+ logger.info "Current position is #{position}"
17
+ position
18
+ end
19
+
20
+ def position=(location)
21
+ super
22
+ out_of_eyes = location.dup.offset_negative(current_position)
23
+ return if out_of_eyes == Applitools::Location::TOP_LEFT
24
+ logger.info "Moving element by #{out_of_eyes} to fit in the eyes region"
25
+
26
+ Applitools::Utils::EyesSeleniumUtils.element_translate_to(
27
+ driver,
28
+ element,
29
+ transforms_offset.offset_negative(out_of_eyes)
30
+ )
31
+ logger.info 'Done scrolling element!'
32
+ end
33
+
34
+ private
35
+
36
+ def transforms_offset
37
+ logger.info 'Getting element transforms...'
38
+ transforms = Applitools::Utils::EyesSeleniumUtils.current_element_transforms(driver, element)
39
+ logger.info "Current transforms: #{transforms}"
40
+ transform_positions = transforms.values.compact.select { |s| !s.empty? }
41
+ .map { |s| get_position_from_transform(s) }
42
+ transform_positions.each do |p|
43
+ raise Applitools::EyesError.new 'Got different css positions!' unless p == transform_positions[0]
44
+ end
45
+ transform_positions[0] || Applitools::Location::TOP_LEFT.dup
46
+ end
47
+ end
48
+ end
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'css_transform/css_transform'
4
+
1
5
  module Applitools::Selenium
2
6
  class CssTranslatePositionProvider
3
7
  extend Forwardable
8
+ include Applitools::CssTransform
4
9
 
5
10
  def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
6
11
 
@@ -33,7 +38,7 @@ module Applitools::Selenium
33
38
  #
34
39
  # @param [Applitools::Location] value The location.
35
40
  def restore_state(value)
36
- transforms = value.values.select { |el| !el.empty? }
41
+ transforms = value.values.compact.select { |el| !el.empty? }
37
42
  Applitools::Utils::EyesSeleniumUtils.set_transforms(executor, value)
38
43
  if transforms.empty?
39
44
  self.last_state_position = Applitools::Location::TOP_LEFT
@@ -62,7 +67,7 @@ module Applitools::Selenium
62
67
  # Gets the entire size of the frame.
63
68
  #
64
69
  # @return [Applitools::RectangleSize] The entire size of the frame.
65
- def entire_size
70
+ def entire_size(_image_width, _image_height)
66
71
  viewport_size = Applitools::Utils::EyesSeleniumUtils.extract_viewport_size(executor)
67
72
  result = Applitools::Utils::EyesSeleniumUtils.current_frame_content_entire_size(executor)
68
73
  logger.info "Entire size: #{result}"
@@ -73,19 +78,33 @@ module Applitools::Selenium
73
78
  result.height = [viewport_size.height, result.height].min if disable_vertical
74
79
  logger.info "Actual size to scroll: #{result}"
75
80
  result
81
+
82
+ # return result unless executor.frame_chain.empty?
83
+
84
+ # spp = Applitools::Selenium::ScrollPositionProvider.new(executor)
85
+ # original_scroll_position = spp.current_position
86
+ # spp.scroll_to_bottom_right
87
+ # bottom_right_position = spp.current_position
88
+ # spp.restore_state(original_scroll_position)
89
+ # Applitools::RectangleSize.new(bottom_right_position.x + image_width, bottom_right_position.y + image_height)
76
90
  end
77
91
 
78
92
  private
79
93
 
80
94
  attr_accessor :executor, :disable_horizontal, :disable_vertical, :max_width, :max_height
81
95
 
82
- def get_position_from_transform(transform)
83
- regexp = /^translate\(\s*(\-?)(\d+)px,\s*(\-?)(\d+)px\s*\)/
84
- data = regexp.match(transform)
85
- raise Applitools::EyesError.new "Can't parse CSS transition: #{transform}!" unless data
86
- x = data[1].empty? ? data[2].to_i : -1 * data[2].to_i
87
- y = data[3].empty? ? data[4].to_i : -1 * data[4].to_i
88
- Applitools::Location.new(x, y)
89
- end
96
+ # def get_position_from_transform(transform)
97
+ # regexp = /^translate\(\s*(\-?)([\d, \.]+)px,\s*(\-?)([\d, \.]+)px\s*\)/
98
+ # data = regexp.match(transform)
99
+ #
100
+ # raise Applitools::EyesError.new "Can't parse CSS transition: #{transform}!" unless data
101
+ # x = data[2].to_f.round
102
+ # y = data[4].to_f.round
103
+ #
104
+ # x *= (-1) unless data[1].empty?
105
+ # y *= (-1) unless data[3].empty?
106
+ #
107
+ # Applitools::Location.new(x, y)
108
+ # end
90
109
  end
91
110
  end
@@ -0,0 +1,113 @@
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)
35
+ end
36
+ end
37
+ dom_tree
38
+ end
39
+
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
52
+ end
53
+ end
54
+
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?
62
+ 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?
65
+ end
66
+ end
67
+ end
68
+ iterate_child_nodes.call(child_nodes)
69
+ end
70
+
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
93
+ end
94
+ end
95
+ end
96
+ 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}")
105
+ end
106
+
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
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,103 @@
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};
12
+ }
13
+ });
14
+ }
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;
25
+ }
26
+ function notEmptyObj(obj) {
27
+ return Object.keys(obj).length ? obj : undefined;
28
+ }
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;
38
+ }
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
+ }
56
+ }
57
+ }
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
+ }
69
+ }
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
+ }
78
+ function captureTextNode(node) {
79
+ return {
80
+ tagName: '#text',
81
+ text: node.textContent,
82
+ };
83
+ }
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;
96
+ }
97
+ }
98
+ return captureNode(document.documentElement);
99
+ }
100
+ return captureFrame(arguments[0]);
101
+ SCRIPT
102
+ end
103
+ end