eyes_selenium 3.14.3 → 3.14.4

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