blinkr 0.2.9 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 39d18a782d87077bd68ed1c41307c385f082b4fc
4
- data.tar.gz: 87a369d2d62eb1ac451b1500222469e20a454357
3
+ metadata.gz: 2b54b1fe0983042827e5f00868dcf9e65ef5c2f2
4
+ data.tar.gz: f90a1abe798a9e3cfaebe7544f0e618b4379116d
5
5
  SHA512:
6
- metadata.gz: ebe1ff145b684dc28e397c5f2cf87ca392d52535c807d8be53452746c564e1e323eaa71bb23a67a1c1c07ddac2cc22df0080b124d74828183defb1af6ae7d7d2
7
- data.tar.gz: 0a20c4ee727298906e7f46b82a622cec2a83f2ce67e529cc29ed9f07df6c35c1954aee150554aba4e60435392dccf2873774eb469c0589d11d776b54b4b1fd65
6
+ metadata.gz: bdbee3d503b16a6354a29171b775ff5bfb1667203529b158cc2e26ce3f58749402fea5098dd1a349387050d76e8454861dbe8919d8473cdf57e7d6205b6503c7
7
+ data.tar.gz: bfc4cc60420fed8c53c20bcec5b224fb341a3e3a3d53a054aaa2e4cdff2575d5f5b0f62b391c459b4df89cae5875e7b1353ab23723ac99138cd70d13aba7d460
data/Gemfile CHANGED
@@ -1,4 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ platform :jruby do
4
+ gem 'manticore'
5
+ end
3
6
  # Specify your gem's dependencies in blinkr.gemspec
4
7
  gemspec
data/bin/blinkr CHANGED
@@ -22,6 +22,7 @@ OptionParser.new do |opts|
22
22
  end
23
23
  opts.on("-s", "--single-url URL", "test a single URL, outputting the response to the console") do |opt|
24
24
  options[:single_url] = opt
25
+ options[:base_url] = opt
25
26
  end
26
27
  end.parse!
27
28
 
data/blinkr.gemspec CHANGED
@@ -4,26 +4,34 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'blinkr/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "blinkr"
7
+ spec.name = 'blinkr'
8
8
  spec.version = Blinkr::VERSION
9
- spec.authors = ["Pete Muir"]
10
- spec.email = ["pmuir@bleepbleep.org.uk"]
9
+ spec.authors = ['Pete Muir', 'Jason Porter']
10
+ spec.email = %w(pmuir@bleepbleep.org.uk lightguard.jp@gmail.com)
11
11
  spec.summary = %q{A simple broken link checker}
12
12
  spec.description = %q{A broken page and link checker for websites. Optionally uses phantomjs to render pages to check resource loading, links created by JS, and report any JS page load errors.}
13
- spec.homepage = "https://github.com/pmuir/blinkr"
14
- spec.license = "Apache-2.0"
13
+ spec.homepage = 'https://github.com/pmuir/blinkr'
14
+ spec.license = 'Apache-2.0'
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
+ spec.files -= ['.gitignore', '.ruby-version', '.ruby-gemset']
18
+
19
+
17
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
22
+ spec.require_paths = ['lib']
20
23
 
21
- spec.required_ruby_version = '~> 2.0'
24
+ spec.required_ruby_version = '~> 1.9'
22
25
 
23
- spec.add_development_dependency "bundler", "~> 1.5"
26
+ spec.add_development_dependency 'bundler', '~> 1.5'
24
27
  spec.add_development_dependency 'rake', '~> 10.3'
25
28
  spec.add_dependency 'nokogiri', '~> 1.5'
26
29
  spec.add_dependency 'typhoeus', '~> 0.7'
27
30
  spec.add_dependency 'slim', '~> 3.0'
28
31
  spec.add_dependency 'parallel', '~> 1.3'
32
+
33
+ if defined? JRUBY_VERSION
34
+ spec.platform = 'java'
35
+ spec.add_dependency 'manticore', '~> 0.4'
36
+ end
29
37
  end
data/lib/blinkr.rb CHANGED
@@ -2,12 +2,13 @@ require 'blinkr/version'
2
2
  require 'blinkr/engine'
3
3
  require 'blinkr/report'
4
4
  require 'blinkr/config'
5
+ require 'blinkr/error'
5
6
  require 'blinkr/typhoeus_wrapper'
6
7
  require 'yaml'
7
8
 
8
9
  module Blinkr
9
10
  def self.run(base_url, config = 'blinkr.yaml', single, verbose, vverbose)
10
- args = { :base_url => base_url, :verbose => verbose, :vverbose => vverbose }
11
+ args = {:base_url => base_url, :verbose => verbose, :vverbose => vverbose}
11
12
  if !config.nil? && File.exists?(config)
12
13
  config = Blinkr::Config.read config, args
13
14
  else
data/lib/blinkr/cache.rb CHANGED
@@ -9,7 +9,11 @@ module Blinkr
9
9
  end
10
10
 
11
11
  def set(request, response)
12
- @memory[request] = response unless response.timed_out?
12
+ if request.is_a? String # HACK for caching resource and js errors
13
+ @memory[request] = response
14
+ else
15
+ @memory[request] = response unless response.timed_out?
16
+ end
13
17
  end
14
18
 
15
19
  def size
data/lib/blinkr/config.rb CHANGED
@@ -8,7 +8,8 @@ module Blinkr
8
8
  Config.new(YAML.load_file(file).merge(args).merge({ :config_file => file }))
9
9
  end
10
10
 
11
- DEFAULTS = {:skips => [], :ignores => [], :max_retrys => 3, :browser => 'typhoeus', :viewport => 1200, :phantomjs_threads => 8, :report => 'blinkr.html'}
11
+ DEFAULTS = {:skips => [], :ignores => [], :max_retrys => 3, :browser => 'typhoeus',
12
+ :viewport => 1200, :phantomjs_threads => 8, :report => 'blinkr.html'}
12
13
 
13
14
  def initialize(hash={})
14
15
  super(DEFAULTS.merge(hash))
data/lib/blinkr/engine.rb CHANGED
@@ -9,6 +9,8 @@ require 'blinkr/extensions/javascript'
9
9
  require 'blinkr/extensions/resources'
10
10
  require 'blinkr/extensions/pipeline'
11
11
  require 'json'
12
+ require 'pathname'
13
+ require 'fileutils'
12
14
  require 'ostruct'
13
15
 
14
16
  # Monkeypatch OpenStruct
@@ -27,7 +29,7 @@ module Blinkr
27
29
  include HttpUtils
28
30
  include Sitemap
29
31
 
30
- def initialize config
32
+ def initialize(config)
31
33
  @config = config.validate
32
34
  @extensions = []
33
35
  load_pipeline
@@ -35,15 +37,25 @@ module Blinkr
35
37
 
36
38
  def run
37
39
  context = OpenStruct.new({:pages => {}})
38
- browser = TyphoeusWrapper.new(@config, context)
40
+ if defined?(JRUBY_VERSION) && @config.browser == 'manticore'
41
+ require 'blinkr/manticore_wrapper'
42
+ bulk_browser = browser = ManticoreWrapper.new(@config, context)
43
+ else
44
+ bulk_browser = browser = TyphoeusWrapper.new(@config, context)
45
+ end
39
46
  browser = PhantomJSWrapper.new(@config, context) if @config.browser == 'phantomjs'
40
47
  page_count = 0
41
- browser.process_all(sitemap_locations, @config.max_page_retrys) do |response, resource_errors, javascript_errors|
48
+ urls = sitemap_locations.uniq
49
+ puts "Fetching #{urls.size} pages from sitemap"
50
+ browser.process_all(urls, @config.max_page_retrys) do |response, resource_errors, javascript_errors|
51
+ url = response.request.base_url
42
52
  if response.success?
43
- url = response.request.base_url
44
53
  puts "Loaded page #{url}" if @config.verbose
45
54
  body = Nokogiri::HTML(response.body)
46
- page = OpenStruct.new({ :response => response, :body => body, :errors => ErrorArray.new(@config), :resource_errors => resource_errors || [], :javascript_errors => javascript_errors || [] })
55
+ page = OpenStruct.new({:response => response, :body => body.freeze,
56
+ :errors => ErrorArray.new(@config),
57
+ :resource_errors => resource_errors || [],
58
+ :javascript_errors => javascript_errors || []})
47
59
  context.pages[url] = page
48
60
  collect page
49
61
  page_count += 1
@@ -51,25 +63,26 @@ module Blinkr
51
63
  puts "#{response.code} #{response.status_message} Unable to load page #{url} #{'(' + response.return_message + ')' unless response.return_message.nil?}"
52
64
  end
53
65
  end
54
- typhoeus.hydra.run if @config.browser == 'typhoeus'
55
- analyze context, typhoeus
56
- puts "Loaded #{page_count} pages using #{browser.name}. Performed #{typhoeus.count} requests using typhoeus."
57
- context.pages.reject! { |url, page| page.errors.empty? }
66
+ puts 'Executing Typhoeus::Hydra.run, this could take awhile' if @config.browser == 'typhoeus'
67
+ # browser.hydra.run if @config.browser == 'typhoeus'
68
+ puts "Loaded #{page_count} pages using #{browser.name}."
69
+ puts 'Analyzing pages'
70
+ analyze context, bulk_browser
71
+ context.pages.reject! { |_, page| page.errors.empty? }
72
+
58
73
  unless @config.export.nil?
59
- File.open(@config.export, 'w') do |file|
60
- file.write(context.to_json)
61
- end
74
+ FileUtils.mkdir_p Pathname.new(@config.report).parent
62
75
  end
63
76
  Blinkr::Report.new(context, self, @config).render
64
77
  end
65
78
 
66
- def append context
67
- exec :append, context
79
+ def append(context)
80
+ execute :append, context
68
81
  end
69
-
70
- def transform page, error, &block
82
+
83
+ def transform(page, error, &block)
71
84
  default = yield
72
- result = exec(:transform, page, error, default)
85
+ result = execute(:transform, page, error, default)
73
86
  if result.empty?
74
87
  default
75
88
  else
@@ -77,33 +90,33 @@ module Blinkr
77
90
  end
78
91
  end
79
92
 
80
- def analyze context, typhoeus
81
- exec :analyze, context, typhoeus
93
+ def analyze(context, typhoeus)
94
+ execute :analyze, context, typhoeus
82
95
  end
83
96
 
84
- def collect page
85
- exec :collect, page
97
+ def collect(page)
98
+ execute :collect, page
86
99
  end
87
100
 
88
101
  private
89
102
 
90
103
  class ErrorArray < Array
91
104
 
92
- def initialize config
105
+ def initialize(config)
93
106
  @config = config
94
107
  end
95
108
 
96
- def << error
97
- unless @config.ignored?(error.url, error.code, error.message)
98
- super
99
- else
109
+ def <<(error)
110
+ if @config.ignored?(error.url, error.code, error.message)
100
111
  self
112
+ else
113
+ super
101
114
  end
102
115
  end
103
116
 
104
117
  end
105
118
 
106
- def extension ext
119
+ def extension(ext)
107
120
  @extensions << ext
108
121
  end
109
122
 
@@ -113,7 +126,7 @@ module Blinkr
113
126
  extension Blinkr::Extensions::Resources.new @config
114
127
  end
115
128
 
116
- def exec method, *args
129
+ def execute(method, *args)
117
130
  result = []
118
131
  @extensions.each do |e|
119
132
  result << e.send(method, *args) if e.respond_to? method
@@ -122,24 +135,24 @@ module Blinkr
122
135
  end
123
136
 
124
137
  def load_pipeline
125
- unless @config.pipeline.nil?
138
+ if @config.pipeline.nil?
139
+ puts 'Loaded default pipeline' if @config.verbose
140
+ default_pipeline
141
+ else
126
142
  pipeline_file = File.join(File.dirname(@config.config_file), @config.pipeline)
127
- if File.exists?( pipeline_file )
128
- p = eval(File.read( pipeline_file ), nil, pipeline_file, 1).load @config
143
+ if File.exists?(pipeline_file)
144
+ p = eval(File.read(pipeline_file), nil, pipeline_file, 1).load @config
129
145
  p.extensions.each do |e|
130
- extension( e )
146
+ extension(e)
131
147
  end
132
148
  if @config.verbose
133
149
  puts "Loaded custom pipeline from #{@config.pipeline}"
134
- pipeline = @extensions.inject{|memo, v| "#{memo}, #{v}" }
150
+ pipeline = @extensions.inject { |memo, v| "#{memo}, #{v}" }
135
151
  puts "Pipeline: #{pipeline}"
136
152
  end
137
153
  else
138
154
  raise "Cannot find pipeline file #{pipeline_file}"
139
155
  end
140
- else
141
- puts "Loaded default pipeline" if @config.verbose
142
- default_pipeline
143
156
  end
144
157
  end
145
158
 
@@ -0,0 +1,30 @@
1
+ module Blinkr
2
+ SEVERITY = [:success, :info, :warning, :danger]
3
+ class Error
4
+ attr_reader :severity, :category, :type, :title, :message, :snippet, :icon, :code, :detail, :url
5
+
6
+ def initialize (opts = {})
7
+ raise TypeError 'severity must be a string or symbol' unless opts[:severity].is_a?(String) || opts[:severity].is_a?(Symbol)
8
+ raise 'severity not a recognized value' unless SEVERITY.include? opts[:severity].to_sym
9
+
10
+ @severity = opts[:severity]
11
+ @category = opts[:category]
12
+ @type = opts[:type]
13
+ @title = opts[:title]
14
+ @message = opts[:message]
15
+ @snippet = opts[:snippet]
16
+ @icon = opts[:icon]
17
+ @code = opts[:code]
18
+ @url = opts[:url]
19
+ @detail = opts[:detail]
20
+ end
21
+
22
+ def to_json(*args)
23
+ content = {}
24
+ instance_variables.each do |v|
25
+ content[v.to_s[1..-1]] = instance_variable_get v
26
+ end
27
+ content.to_json
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,5 @@
1
+ require 'blinkr/error'
2
+
1
3
  module Blinkr
2
4
  module Extensions
3
5
  class ATitle
@@ -8,7 +10,11 @@ module Blinkr
8
10
 
9
11
  def collect page
10
12
  page.body.css('a:not([title])').each do |a|
11
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'SEO', :type => '<a title=""> missing', :title => "#{a['href']} (line #{a.line})", :message => '<a title=""> missing', :snippet => a.to_s, :icon => 'fa-info' })
13
+ page.errors << Blinkr::Error.new({:severity => 'info', :category => 'SEO',
14
+ :type => '<a title=""> missing',
15
+ :title => "#{a['href']} (line #{a.line})",
16
+ :message => '<a title=""> missing',
17
+ :snippet => a.to_s, :icon => 'fa-info'})
12
18
  end
13
19
  end
14
20
 
@@ -1,3 +1,5 @@
1
+ require 'blinkr/error'
2
+
1
3
  module Blinkr
2
4
  module Extensions
3
5
  class EmptyAHref
@@ -9,7 +11,11 @@ module Blinkr
9
11
  def collect page
10
12
  page.body.css('a[href]').each do |a|
11
13
  if a['href'].empty?
12
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'HTML Compatibility/Correctness', :type => '<a href=""> empty', :title => %Q{<a href=""> empty (line #{a.line})}, :message => %Q{<a href=""> empty}, :snippet => a.to_s, :icon => 'fa-info' })
14
+ page.errors << Blinkr::Error.new({:severity => 'info', :category => 'HTML Compatibility/Correctness',
15
+ :type => '<a href=""> empty',
16
+ :title => %Q{<a href=""> empty (line #{a.line})},
17
+ :message => %Q{<a href=""> empty}, :snippet => a.to_s,
18
+ :icon => 'fa-info'})
13
19
  end
14
20
  end
15
21
  end
@@ -1,3 +1,5 @@
1
+ require 'blinkr/error'
2
+
1
3
  module Blinkr
2
4
  module Extensions
3
5
  class ImgAlt
@@ -8,7 +10,11 @@ module Blinkr
8
10
 
9
11
  def collect page
10
12
  page.body.css('img:not([alt])').each do |img|
11
- page.errors << OpenStruct.new({ :severity => 'warning', :category => 'SEO', :type => '<img alt=""> missing', :title => "#{img['src']} (line #{img.line})", :message => '<img alt=""> missing', :snippet => img.to_s, :icon => 'fa-info' })
13
+ page.errors << OpenStruct.new({:severity => 'warning', :category => 'SEO',
14
+ :type => '<img alt=""> missing',
15
+ :title => "#{img['src']} (line #{img.line})",
16
+ :message => '<img alt=""> missing', :snippet => img.to_s,
17
+ :icon => 'fa-info'})
12
18
  end
13
19
  end
14
20
 
@@ -1,3 +1,5 @@
1
+ require 'blinkr/error'
2
+
1
3
  module Blinkr
2
4
  module Extensions
3
5
  class InlineCss
@@ -9,9 +11,18 @@ module Blinkr
9
11
  def collect page
10
12
  page.body.css('[style]').each do |elm|
11
13
  if elm['style'] == ""
12
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'HTML Compatibility/Correctness', :type => 'style attribute is empty', :title => %Q{"#{elm['style']}" (line #{elm.line})}, :message => 'style attribute is empty', :snippet => elm.to_s, :icon => 'fa-info' })
14
+ page.errors << Blinkr::Error.new({:severity => 'info', :category => 'HTML Compatibility/Correctness',
15
+ :type => 'style attribute is empty',
16
+ :title => %Q{"#{elm['style']}" (line #{elm.line})},
17
+ :message => 'style attribute is empty', :snippet => elm.to_s,
18
+ :icon => 'fa-info'})
13
19
  else
14
- page.errors << OpenStruct.new({ :severity => 'info', :category => 'HTML Compatibility/Correctness', :type => 'Inline CSS detected', :title => %Q{"#{elm['style']}" (line #{elm.line})}, :message => 'inline style', :snippet => elm.to_s, :icon => 'fa-info' })
20
+ page.errors << Blinkr::Error.new({:severity => 'info',
21
+ :category => 'HTML Compatibility/Correctness',
22
+ :type => 'Inline CSS detected',
23
+ :title => %Q{"#{elm['style']}" (line #{elm.line})},
24
+ :message => 'inline style', :snippet => elm.to_s,
25
+ :icon => 'fa-info'})
15
26
  end
16
27
  end
17
28
  end
@@ -1,3 +1,5 @@
1
+ require 'blinkr/error'
2
+
1
3
  module Blinkr
2
4
  module Extensions
3
5
  class JavaScript
@@ -8,7 +10,9 @@ module Blinkr
8
10
 
9
11
  def collect page
10
12
  page.javascript_errors.each do |error|
11
- page.errors << OpenStruct.new({ :severity => 'danger', :category => 'JavaScript', :type => 'JavaScript error', :title => error['msg'], :snippet => error['trace'], :icon => 'fa-gears' })
13
+ page.errors << Blinkr::Error.new(:severity => 'danger', :category => 'JavaScript',
14
+ :type => 'JavaScript error', :title => error['msg'],
15
+ :snippet => error['trace'], :icon => 'fa-gears')
12
16
  end
13
17
  end
14
18
 
@@ -1,50 +1,94 @@
1
+ require 'uri'
2
+ require 'blinkr/error'
1
3
  require 'blinkr/http_utils'
2
4
 
3
5
  module Blinkr
4
6
  module Extensions
5
7
  class Links
6
- include HttpUtils
8
+ include Blinkr::HttpUtils
7
9
 
8
- def initialize config
10
+ def initialize(config)
9
11
  @config = config
10
12
  @links = {}
11
13
  end
12
14
 
13
- def collect page
15
+ def collect(page)
14
16
  page.body.css('a[href]').each do |a|
15
17
  attr = a.attribute('href')
16
18
  src = page.response.effective_url
17
19
  url = attr.value
18
- url = sanitize url, src
19
- unless url.nil? || @config.skipped?(url)
20
- @links[url] ||= []
21
- @links[url] << {:page => page, :line => attr.line, :snippet => attr.parent.to_s}
20
+ unless @config.skipped?(url)
21
+ url = sanitize url, src
22
+ unless url.nil?
23
+ @links[url] ||= []
24
+ @links[url] << {:page => page, :line => attr.line, :snippet => attr.parent.to_s}
25
+ end
22
26
  end
23
27
  end
24
28
  end
25
29
 
26
- def analyze context, typhoeus
27
- puts "----------------------" if @config.verbose
28
- puts " #{@links.length} links to check " if @config.verbose
29
- puts "----------------------" if @config.verbose
30
- @links.each do |url, metadata|
31
- typhoeus.process(url, @config.max_retrys) do |resp|
32
- puts "Loaded #{url} via typhoeus #{'(cached)' if resp.cached?}" if @config.verbose
33
- unless resp.success? || resp.code == 200
30
+ def analyze(context, browser)
31
+ puts '----------------------'
32
+ puts " #{@links.length} links to check "
33
+ puts '----------------------'
34
+ external_links = @links.reject { |k| k.start_with? @config.base_url }
35
+ processed = 0
36
+ # Find the internal links
37
+ @links.select{|k| k.start_with? @config.base_url}.each do |url, locations|
38
+ link = URI.parse(url)
39
+ link.fragment = nil
40
+ link.query = nil
41
+ unless context.pages.keys.include?(link.to_s) || context.pages.keys.include?((link.to_s + '/'))
42
+ locations.each do |location|
43
+ location[:page].errors << Blinkr::Error.new({:severity => :warning,
44
+ :category => 'Resource missing from sitemap',
45
+ :type => '<a href=""> target missing from sitemap',
46
+ :url => url, :title => "#{url} (line #{location[:line]})",
47
+ :code => nil,
48
+ :message => 'Missing from sitemap',
49
+ :detail => 'Checked with Typheous',
50
+ :snippet => location[:snippet],
51
+ :icon => 'fa-bookmark-o'
52
+ })
53
+ # It wasn't in the sitemap, so we'll add it to the "external_links" to still be checked
54
+ external_links[url] = locations
55
+ end
56
+ end
57
+ end
58
+ external_links.each do |url, metadata|
59
+ # if link start_with? @config.base_url check to see if it's in the sitemap.xml
60
+ browser.process(url, @config.max_retrys, :method => :get, :followlocation => true) do |resp|
61
+ puts "Loaded #{url} via #{browser.name} #{'(cached)' if resp.cached?}" if @config.verbose
62
+ if resp.code.to_i < 200 || resp.code.to_i > 300
63
+ response = resp
64
+
34
65
  metadata.each do |src|
35
- code = resp.code.to_i unless resp.code.nil? || resp.code == 0
36
- if resp.status_message.nil?
37
- message = resp.return_message
66
+ detail = nil
67
+ if response.status_message.nil?
68
+ message = response.return_message
38
69
  else
39
- message = resp.status_message
40
- detail = resp.return_message unless resp.return_message == "No error"
70
+ message = response.status_message
71
+ detail = response.return_message unless resp.return_message == 'No error'
72
+ end
73
+
74
+ severity = :danger
75
+ if response.code.to_i >= 300 && response.code.to_i < 400
76
+ severity = :warning
41
77
  end
42
- src[:page].errors << OpenStruct.new({ :severity => 'danger', :category => 'Resources missing', :type => '<a href=""> target cannot be loaded', :url => url, :title => "#{url} (line #{src[:line]})", :code => code, :message => message, :detail => detail, :snippet => src[:snippet], :icon => 'fa-bookmark-o' })
78
+ src[:page].errors << Blinkr::Error.new({:severity => severity,
79
+ :category => 'Resources missing',
80
+ :type => '<a href=""> target cannot be loaded',
81
+ :url => url, :title => "#{url} (line #{src[:line]})",
82
+ :code => response.code.to_i, :message => message,
83
+ :detail => detail, :snippet => src[:snippet],
84
+ :icon => 'fa-bookmark-o'}) unless response.success?
43
85
  end
44
86
  end
87
+ processed += 1
88
+ puts "Processed #{processed} of #{external_links.size}" if @config.verbose
45
89
  end
46
90
  end
47
- typhoeus.hydra.run
91
+ browser.hydra.run if browser.is_a? Blinkr::TyphoeusWrapper
48
92
  end
49
93
 
50
94
  end