eyes_selenium 6.12.10 → 6.12.11

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/eyes_selenium.gemspec +2 -5
  4. data/lib/applitools/eyes_selenium/version.rb +1 -1
  5. data/lib/applitools/selenium/browsers_info.rb +2 -0
  6. data/lib/applitools/selenium/concerns/selenium_eyes.rb +0 -33
  7. data/lib/applitools/selenium/driver.rb +1 -1
  8. data/lib/applitools/selenium/eyes.rb +1 -5
  9. data/lib/applitools/selenium/selenium_eyes.rb +15 -315
  10. data/lib/applitools/selenium/visual_grid/visual_grid_runner.rb +33 -92
  11. data/lib/eyes_selenium.rb +0 -2
  12. metadata +9 -64
  13. data/lib/applitools/selenium/css_parser/find_embedded_resources.rb +0 -102
  14. data/lib/applitools/selenium/dom_capture/dom_capture.rb +0 -172
  15. data/lib/applitools/selenium/dom_capture/dom_capture_script.rb +0 -611
  16. data/lib/applitools/selenium/external_css_resources.rb +0 -32
  17. data/lib/applitools/selenium/visual_grid/dom_snapshot_script.rb +0 -198
  18. data/lib/applitools/selenium/visual_grid/eyes_connector.rb +0 -170
  19. data/lib/applitools/selenium/visual_grid/render_info.rb +0 -24
  20. data/lib/applitools/selenium/visual_grid/render_request.rb +0 -24
  21. data/lib/applitools/selenium/visual_grid/render_requests.rb +0 -12
  22. data/lib/applitools/selenium/visual_grid/render_task.rb +0 -311
  23. data/lib/applitools/selenium/visual_grid/resource_cache.rb +0 -69
  24. data/lib/applitools/selenium/visual_grid/running_test.rb +0 -271
  25. data/lib/applitools/selenium/visual_grid/thread_pool.rb +0 -95
  26. data/lib/applitools/selenium/visual_grid/vg_match_window_data.rb +0 -187
  27. data/lib/applitools/selenium/visual_grid/vg_region.rb +0 -16
  28. data/lib/applitools/selenium/visual_grid/vg_resource.rb +0 -77
  29. data/lib/applitools/selenium/visual_grid/vg_task.rb +0 -53
  30. data/lib/applitools/selenium/visual_grid/visual_grid_eyes.rb +0 -494
  31. data/lib/applitools/selenium/visual_grid/web_element_region.rb +0 -16
@@ -1,110 +1,51 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Applitools
3
4
  module Selenium
4
- class VisualGridRunner < ::Applitools::ClassicRunner
5
- # rubocop:disable Style/MutableConstant
6
- EMPTY_QUEUE = []
7
- attr_accessor :all_eyes, :resource_cache, :put_cache, :rendering_info, :render_queue
8
- # rubocop:enable Style/MutableConstant
9
-
10
- alias queue render_queue
11
-
12
- def initialize(concurrent_open_sessions = 10, mask_log = false)
13
- super(mask_log=mask_log)
14
- self.all_eyes = []
15
- self.render_queue = []
16
- @thread_pool = Applitools::Selenium::VGThreadPool.new(concurrent_open_sessions)
17
- self.resource_cache = Applitools::Selenium::ResourceCache.new
18
- self.put_cache = Applitools::Selenium::ResourceCache.new
19
- init
20
- end
21
-
22
- def init
23
- @thread_pool.on_next_task_needed do
24
- (task = get_task_queue.pop).is_a?(Applitools::Selenium::VGTask) ? task : nil
5
+ class VisualGridRunner < Applitools::EyesRunner
6
+ attr_accessor :all_test_results, :all_pending_exceptions
7
+ attr_reader :test_concurrency
8
+
9
+ DEFAULT_CONCURRENCY = 5
10
+
11
+ def initialize(concurrency_or_options = DEFAULT_CONCURRENCY, mask_log = nil)
12
+ if concurrency_or_options.is_a?(Hash)
13
+ @test_concurrency = concurrency_or_options[:test_concurrency] || DEFAULT_CONCURRENCY
14
+ mask_log = concurrency_or_options[:mask_log]
15
+ else
16
+ @test_concurrency = concurrency_or_options || DEFAULT_CONCURRENCY
25
17
  end
26
- @thread_pool.start
27
- end
28
-
29
- def open(eyes)
30
- all_eyes << eyes
18
+ super(mask_log)
19
+ self.all_test_results = []
20
+ self.all_pending_exceptions = {}
31
21
  end
32
22
 
33
- def enqueue_render_task(render_task)
34
- render_queue.push render_task if render_task.is_a? Applitools::Selenium::RenderTask
23
+ def aggregate_result(test_result)
24
+ Applitools::ArgumentGuard.is_a?(test_result, 'test_result', Applitools::TestResults)
25
+ all_test_results << test_result
35
26
  end
36
27
 
37
- def stop
38
- sleep 0.5 while all_running_tests.map(&:score).reduce(0, :+) > 0
39
- @thread_pool.stop
40
- end
41
-
42
- def rendering_info(connector)
43
- @rendering_info ||= connector.rendering_info
28
+ def aggregate_exceptions(result, exception)
29
+ all_pending_exceptions[result] = exception
44
30
  end
45
31
 
46
32
  def get_all_test_results(throw_exception = false)
47
- all_tasks_completed = proc do
48
- all_running_tests.count == 0 ||
49
- (states = all_running_tests.map(&:state_name).uniq).count == 1 && states.first == :completed
50
- end
51
-
52
- sleep 0.5 until all_tasks_completed.call
53
-
54
- failed_results = all_test_results.select { |r| !r.as_expected? }
55
- failed_results.each do |r|
56
- exception = Applitools::NewTestError.new new_test_error_message(r), r if r.new?
57
- exception = Applitools::DiffsFoundError.new diffs_found_error_message(r), r if r.unresolved? && !r.new?
58
- exception = Applitools::TestFailedError.new test_failed_error_message(r), r if r.failed?
59
- aggregate_exceptions(r, exception)
33
+ begin
34
+ if throw_exception
35
+ all_pending_exceptions.each do |_result, exception|
36
+ raise exception
37
+ end
38
+ end
39
+ ensure
40
+ delete_all_batches
60
41
  end
61
- super
42
+ all_universal_results = close_all_eyes
43
+ Applitools::TestResultSummary.new(all_universal_results)
62
44
  end
63
45
 
64
46
  def universal_eyes_manager_config
65
- Applitools::UniversalEyesManagerConfig.vg(@thread_pool.concurrency)
66
- end
67
-
68
- private
69
-
70
- def all_running_tests
71
- all_eyes.collect(&:test_list).flatten
72
- end
73
-
74
- def all_running_tests_by_score
75
- all_running_tests.sort { |x, y| y.score <=> x.score }
76
- end
77
-
78
- # rubocop:disable Style/AccessorMethodName
79
- def get_task_queue
80
- test_to_run = if render_queue.empty?
81
- all_running_tests_by_score.first
82
- else
83
- self
84
- end
85
- test_to_run ? test_to_run.queue : EMPTY_QUEUE
86
- end
87
- # rubocop:enable Style/AccessorMethodName
88
-
89
- def new_test_error_message(result)
90
- original_results = result.original_results
91
- "New test '#{original_results['name']}' " \
92
- "of '#{original_results['appName']}' " \
93
- "Please approve the baseline at #{original_results['appUrls']['session']} "
94
- end
95
-
96
- def diffs_found_error_message(result)
97
- original_results = result.original_results
98
- "Test '#{original_results['name']}' " \
99
- "of '#{original_results['appname']}' " \
100
- "detected differences! See details at #{original_results['appUrls']['session']}"
101
- end
102
-
103
- def test_failed_error_message(result)
104
- original_results = result.original_results
105
- "Test '#{original_results['name']}' of '#{original_results['appName']}' " \
106
- "is failed! See details at #{original_results['appUrls']['session']}"
47
+ Applitools::UniversalEyesManagerConfig.vg(test_concurrency)
107
48
  end
108
49
  end
109
50
  end
110
- end
51
+ end
data/lib/eyes_selenium.rb CHANGED
@@ -45,8 +45,6 @@ Applitools::Selenium.require_dir 'selenium/scripts'
45
45
  Applitools::Selenium.require_dir 'selenium'
46
46
  Applitools::Selenium.require_dir 'eyes_selenium'
47
47
  Applitools::Selenium.require_dir 'selenium/visual_grid'
48
- Applitools::Selenium.require_dir 'selenium/dom_capture'
49
- Applitools::Selenium.require_dir 'selenium/css_parser'
50
48
 
51
49
  if defined? Selenium::WebDriver::Driver
52
50
  Selenium::WebDriver::Driver.class_eval do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eyes_selenium
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.12.10
4
+ version: 6.12.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Applitools Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-07 00:00:00.000000000 Z
11
+ date: 2025-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eyes_core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 6.9.10
19
+ version: 6.9.11
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 6.9.10
26
+ version: 6.9.11
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: selenium-webdriver
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -31,55 +31,19 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '3'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
34
+ - - "<"
39
35
  - !ruby/object:Gem::Version
40
- version: '3'
41
- - !ruby/object:Gem::Dependency
42
- name: css_parser
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
36
+ version: '5'
48
37
  type: :runtime
49
38
  prerelease: false
50
39
  version_requirements: !ruby/object:Gem::Requirement
51
40
  requirements:
52
41
  - - ">="
53
42
  - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: crass
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: state_machines
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
43
+ version: '3'
44
+ - - "<"
81
45
  - !ruby/object:Gem::Version
82
- version: '0'
46
+ version: '5'
83
47
  description: Provides SDK for writing Applitools Selenium-based tests
84
48
  email:
85
49
  - team@applitools.com
@@ -100,19 +64,15 @@ files:
100
64
  - lib/applitools/selenium/concerns/selenium_eyes.rb
101
65
  - lib/applitools/selenium/configuration.rb
102
66
  - lib/applitools/selenium/context_based_scale_provider.rb
103
- - lib/applitools/selenium/css_parser/find_embedded_resources.rb
104
67
  - lib/applitools/selenium/css_transform/css_transform.rb
105
68
  - lib/applitools/selenium/css_translate_element_position_provider.rb
106
69
  - lib/applitools/selenium/css_translate_position_provider.rb
107
70
  - lib/applitools/selenium/device_name_generated.rb
108
71
  - lib/applitools/selenium/devices.rb
109
- - lib/applitools/selenium/dom_capture/dom_capture.rb
110
- - lib/applitools/selenium/dom_capture/dom_capture_script.rb
111
72
  - lib/applitools/selenium/driver.rb
112
73
  - lib/applitools/selenium/element.rb
113
74
  - lib/applitools/selenium/element_position_provider.rb
114
75
  - lib/applitools/selenium/entire_element_screenshot.rb
115
- - lib/applitools/selenium/external_css_resources.rb
116
76
  - lib/applitools/selenium/eyes.rb
117
77
  - lib/applitools/selenium/eyes_screenshot.rb
118
78
  - lib/applitools/selenium/eyes_target_locator.rb
@@ -147,29 +107,14 @@ files:
147
107
  - lib/applitools/selenium/visual_grid/android_multi_device_target.rb
148
108
  - lib/applitools/selenium/visual_grid/chrome_emulation_info.rb
149
109
  - lib/applitools/selenium/visual_grid/desktop_browser_info.rb
150
- - lib/applitools/selenium/visual_grid/dom_snapshot_script.rb
151
110
  - lib/applitools/selenium/visual_grid/emulation_base_info.rb
152
- - lib/applitools/selenium/visual_grid/eyes_connector.rb
153
111
  - lib/applitools/selenium/visual_grid/i_render_browser_info.rb
154
112
  - lib/applitools/selenium/visual_grid/ios_device_info.rb
155
113
  - lib/applitools/selenium/visual_grid/ios_device_name.rb
156
114
  - lib/applitools/selenium/visual_grid/ios_device_name_generated.rb
157
115
  - lib/applitools/selenium/visual_grid/ios_multi_device_target.rb
158
116
  - lib/applitools/selenium/visual_grid/render_browser_info.rb
159
- - lib/applitools/selenium/visual_grid/render_info.rb
160
- - lib/applitools/selenium/visual_grid/render_request.rb
161
- - lib/applitools/selenium/visual_grid/render_requests.rb
162
- - lib/applitools/selenium/visual_grid/render_task.rb
163
- - lib/applitools/selenium/visual_grid/resource_cache.rb
164
- - lib/applitools/selenium/visual_grid/running_test.rb
165
- - lib/applitools/selenium/visual_grid/thread_pool.rb
166
- - lib/applitools/selenium/visual_grid/vg_match_window_data.rb
167
- - lib/applitools/selenium/visual_grid/vg_region.rb
168
- - lib/applitools/selenium/visual_grid/vg_resource.rb
169
- - lib/applitools/selenium/visual_grid/vg_task.rb
170
- - lib/applitools/selenium/visual_grid/visual_grid_eyes.rb
171
117
  - lib/applitools/selenium/visual_grid/visual_grid_runner.rb
172
- - lib/applitools/selenium/visual_grid/web_element_region.rb
173
118
  - lib/eyes_selenium.rb
174
119
  homepage: https://www.applitools.com
175
120
  licenses:
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'crass'
4
-
5
- module Applitools
6
- module Selenium
7
- module CssParser
8
- class FindEmbeddedResources
9
- class << self
10
- end
11
-
12
- class CssParseError < Applitools::EyesError; end
13
-
14
- attr_accessor :css
15
-
16
- def initialize(css)
17
- self.css = css
18
- @nodes_by_type = {}
19
- end
20
-
21
- def imported_css
22
- fetch_urls(import_rules)
23
- end
24
-
25
- def fonts
26
- fetch_urls(font_face_rules)
27
- end
28
-
29
- def images
30
- fetch_urls(images_rules)
31
- end
32
-
33
- private
34
-
35
- def url(node)
36
- result = []
37
- node[:tokens].select { |t| t[:node] == :url }.uniq.each do |nd|
38
- result << nd[:value] if nd && !nd.empty?
39
- end
40
- node[:tokens].select { |t| t[:node] == :function && t[:value] == 'url' }.uniq.each do |nd|
41
- url_index = node[:tokens].index(nd)
42
- url_string_node = url_index && node[:tokens][url_index + 1]
43
- result << (
44
- url_string_node && url_string_node[:node] == :string &&
45
- !url_string_node[:value].empty? && url_string_node[:value]
46
- )
47
- end
48
- result.compact
49
- end
50
-
51
- def fetch_urls(nodes)
52
- nodes.map { |n| url(n) }.flatten.compact
53
- end
54
-
55
- def import_rules
56
- # css_nodes.select { |n| n[:node] == :at_rule && n[:name] == 'import' }
57
- nodes_by_type[:import_rules]
58
- end
59
-
60
- def font_face_rules
61
- # css_nodes.select { |n| n[:node] == :at_rule && n[:name] == 'font-face' }
62
- nodes_by_type[:font_face_rules]
63
- end
64
-
65
- def images_rules
66
- # css_nodes.select { |n| n[:node] == :style_rule }.map { |n| n[:children] }
67
- # .flatten
68
- # .select { |n| n[:node] == :property && (n[:name] == 'background' || n[:name] == 'background-image') }
69
- nodes_by_type[:images_rules]
70
- end
71
-
72
- def css_nodes
73
- @css_nodes ||= parse_nodes
74
- end
75
-
76
- def nodes_by_type
77
- return @nodes_by_type unless @nodes_by_type.empty?
78
- @nodes_by_type[:import_rules] ||= []
79
- @nodes_by_type[:font_face_rules] ||= []
80
- @nodes_by_type[:images_rules] ||= []
81
- css_nodes.each do |n|
82
- @nodes_by_type[:import_rules] << n if n[:node] == :at_rule && n[:name] == 'import'
83
- @nodes_by_type[:font_face_rules] << n if n[:node] == :at_rule && n[:name] == 'font-face'
84
- @nodes_by_type[:images_rules] << n if n[:node] == :style_rule
85
- end
86
-
87
- unless @nodes_by_type[:images_rules].nil?
88
- @nodes_by_type[:images_rules].map! { |n| n[:children] }.flatten!
89
- @nodes_by_type[:images_rules] = @nodes_by_type[:images_rules].select do |n|
90
- n[:node] == :property && (n[:name] == 'background' || n[:name] == 'background-image')
91
- end
92
- end
93
- @nodes_by_type
94
- end
95
-
96
- def parse_nodes
97
- Crass.parse css
98
- end
99
- end
100
- end
101
- end
102
- end
@@ -1,172 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Applitools
4
- module Selenium
5
- module DomCapture
6
- extend self
7
- DOM_EXTRACTION_TIMEOUT = 300 # seconds
8
- def full_window_dom(driver, server_connector, logger, position_provider = nil)
9
- return get_dom(driver, server_connector, logger) unless position_provider
10
- scroll_top_and_return_back(position_provider) do
11
- get_dom(driver, server_connector, logger)
12
- end
13
- end
14
-
15
- def get_dom(driver, server_connector, logger)
16
- original_frame_chain = driver.frame_chain
17
- dom = get_frame_dom(driver, server_connector, logger)
18
- unless original_frame_chain.empty?
19
- driver.switch_to.default_content
20
- driver.switch_to.frames(frame_chain: original_frame_chain)
21
- end
22
- # CSS processing
23
- dom
24
- end
25
-
26
- def get_frame_dom(driver, server_connector, logger)
27
- logger.info 'Trying to get DOM from driver'
28
- start_time = Time.now
29
- script_response = nil
30
- loop do
31
- result_as_string = driver.execute_script(CAPTURE_FRAME_SCRIPT + ' return __captureDomAndPoll();')
32
- script_response = Oj.load(result_as_string)
33
- status = script_response['status']
34
- break if status == 'SUCCESS'
35
- raise Applitools::EyesError, 'DOM extraction timeout!' if Time.now - start_time > DOM_EXTRACTION_TIMEOUT
36
- raise Applitools::EyesError, "DOM extraction error: #{script_response['error']}" if script_response['error']
37
- sleep(0.2)
38
- end
39
- response_lines = script_response['value'].split(/\r?\n/)
40
- separators = Oj.load(response_lines.shift)
41
- missing_css_list = []
42
- missing_frame_list = []
43
- data = []
44
-
45
- blocks = DomParts.new(missing_css_list, missing_frame_list, data)
46
- collector = blocks.collectors.next
47
- response_lines.each do |line|
48
- if line == separators['separator']
49
- collector = blocks.collectors.next
50
- else
51
- collector << line
52
- end
53
- end
54
- logger.info "Missing CSS: #{missing_css_list.count}"
55
- logger.info "Missing frames: #{missing_frame_list.count}"
56
-
57
- frame_data = recurse_frames(driver, server_connector, logger, missing_frame_list)
58
- result = replace(separators['iframeStartToken'], separators['iframeEndToken'], data.first, frame_data)
59
- css_data = fetch_css_files(missing_css_list, server_connector)
60
- replace(separators['cssStartToken'], separators['cssEndToken'], result, css_data)
61
- rescue StandardError => e
62
- logger.error(e.class)
63
- logger.error(e.message)
64
- return ''
65
- end
66
-
67
- def fetch_css_files(missing_css_list, server_connector)
68
- result = {}
69
- missing_css_list.each do |url|
70
- next if url.empty?
71
- next if /^blob:/ =~ url
72
-
73
- begin
74
- missing_css_response = server_connector.download_resource(
75
- url,
76
- Applitools::Utils::EyesSeleniumUtils.user_agent(driver)
77
- )
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 =
85
- Applitools::Selenium::CssParser::FindEmbeddedResources.new(css).imported_css.map do |found_url|
86
- base_url(url).merge(found_url).to_s
87
- end
88
- fetch_css_files(found_and_missing_css, server_connector).each do |_k, v|
89
- css += v
90
- end
91
-
92
- result[url] = Oj.dump("\n/** #{url} **/\n" + css).gsub(/^\"|\"$/, '')
93
- rescue StandardError
94
- result[url] = ''
95
- end
96
- end
97
- result
98
- end
99
-
100
- def recurse_frames(driver, server_connector, logger, missing_frame_list)
101
- return if missing_frame_list.empty?
102
- frame_data = {}
103
- frame_chain = driver.frame_chain
104
- origin_location = driver.execute_script('return document.location.href')
105
- missing_frame_list.each do |missing_frame_line|
106
- logger.info "Switching to frame line: #{missing_frame_line}"
107
- missing_frame_line.split(/,/).each do |xpath|
108
- logger.info "switching to specific frame: #{xpath}"
109
- begin
110
- frame_element = driver.find_element(:xpath, xpath)
111
- frame_src = frame_element.attribute('src')
112
- driver.switch_to.frame(frame_element)
113
- logger.info "Switched to frame ( #{xpath} ) with src( #{frame_src} )"
114
- ensure
115
- location_after_switch = driver.execute_script('return document.location.href')
116
-
117
- if origin_location == location_after_switch
118
- logger.info "Switch to frame (#{missing_frame_line}) failed"
119
- frame_data[missing_frame_line] = ''
120
- else
121
- result = get_frame_dom(driver, server_connector, logger)
122
- frame_data[missing_frame_line] = result
123
- end
124
-
125
- driver.switch_to.default_content
126
- driver.switch_to.frames(frame_chain: frame_chain)
127
- end
128
- end
129
- end
130
- frame_data
131
- end
132
-
133
- def scroll_top_and_return_back(position_provider)
134
- original_position = position_provider.current_position
135
- return yield if block_given? && original_position.nil?
136
- position_provider.scroll_to Applitools::Location.new(0, 0)
137
- result = yield if block_given?
138
- position_provider.scroll_to original_position
139
- result
140
- end
141
-
142
- def replace(open_token, close_token, input, replacements)
143
- pattern = /#{open_token}(?<key>.+?)#{close_token}/
144
- input.gsub(pattern) { |_m| replacements[Regexp.last_match(1)] }
145
- end
146
-
147
- def base_url(url)
148
- uri = URI.parse(url)
149
- uri.query = uri.fragment = nil
150
- uri.path = ''
151
- uri
152
- end
153
-
154
- class DomParts
155
- attr_accessor :dom_part_collectors
156
- def initialize(*args)
157
- self.dom_part_collectors = args
158
- @index = 0
159
- end
160
-
161
- def collectors
162
- @collectors ||= Enumerator.new(dom_part_collectors.length) do |y|
163
- loop do
164
- y << dom_part_collectors[@index]
165
- @index += 1
166
- end
167
- end
168
- end
169
- end
170
- end
171
- end
172
- end