blinkr 0.0.2 → 0.1.0

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
  SHA1:
3
- metadata.gz: e0304997ca5aeeeba1b12e5a5f199087f1a04586
4
- data.tar.gz: e49c5e7bbf7a4cfcbb267c5fed0fe12d3674e232
3
+ metadata.gz: d8e918082f460b113ad7517dc20f4838c1b3052d
4
+ data.tar.gz: 523df75b0eb689beb7ce691371c4e0fc8eb422c0
5
5
  SHA512:
6
- metadata.gz: 90466710ddd2e007a02f360fa11fb5a703b3e03aaf09364e7f02d0a880d682e74c60ba84b6e964be08d872ce643b7659e2bb4e2c9ac91c23bb9f4d3470786031
7
- data.tar.gz: bfa50f7dd57cd512f6dce509c0e99c640768a290129d86577ff33f919125e9b71fc43c7578d96c61a2b24c5df9e09870ebde9fe907a13759648b36f69d425163
6
+ metadata.gz: aac832c6fb06a704a95d91249b64d278c694c6b990b30954ac1daacb8f93f721595f2fa9ae2daecc474eb5bd72415b315fc509a528dbb019b7a339326e9aa679
7
+ data.tar.gz: ad1d2fc269e08db56f02eee1b0f4d101435d026c4de6357e750ea96f3e2dd8888aa332f686fa8ca453f7635a3e7bfb7acb9e82fbb4fba502ce88d3e61ea2ab7b
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Blinkr
2
2
 
3
- A broken link checker for websites
3
+ A broken page and link checker for websites. Optionally uses phantomjs to render pages
4
+ to check resource loading, links created by JS, and report any JS page load errors.
5
+
6
+ typhoeus, which can execute up to 200 parallel requests, and cache the results is used
7
+ to check links.
4
8
 
5
9
  ## Installation
6
10
 
@@ -16,9 +20,13 @@ Or install it yourself as:
16
20
 
17
21
  $ gem install blinkr
18
22
 
23
+ If you wish to use phantomjs, install phantomjs for your platform
24
+ http://phantomjs.org/download.html
25
+
19
26
  ## Usage
20
27
 
21
- To run blinkr against your site, assuming you have a `sitemap.xml` in the root of your site:
28
+ Blinkr determines which pages to load from your `sitemap.xml`. To run blinkr
29
+ against your site checking every `a[href]` link on all your pages:
22
30
 
23
31
  ````
24
32
  blinkr -u http://www.jboss.org
@@ -26,26 +34,87 @@ blinkr -u http://www.jboss.org
26
34
 
27
35
  If you want to customize blinkr, create a config file `blinkr.yaml`. For example:
28
36
 
37
+
29
38
  ````
30
- # Links not to check
39
+ # Links and pages not to check (may be a regexp or a string)
31
40
  skips:
32
41
  - !ruby/regexp /^\/video\/((?!91710755).)*\/$/
33
42
  - !ruby/regexp /^\/quickstarts\/((?!eap\/kitchensink).)*\/*$/
34
43
  - !ruby/regexp /^\/boms\/((?!eap\/jboss-javaee-6_0).)*\/*$/
35
44
  - !ruby/regexp /^\/archetypes\/((?!eap\/jboss-javaee6-webapp-archetype).)*\/*$/
45
+
46
+ # Errors to ignore when generating the output. Each ignore should be a hash
47
+ # containing a url (may be regexp or a string), an error code (integer) and a
48
+ # error message (may be a regexp or a string)
49
+ ignores:
50
+ - url: http://www.acme.com/foo
51
+ message: Not Found
52
+ - url: !ruby/regexp /^https?:\/\/(www\.)?acme\.com\/bar\/
53
+ code: 500
54
+
36
55
  # The output file to write the report to
37
56
  report: _tmp/blinkr.html
38
- # The URL to check
57
+
58
+ # The URL to check (often specificed on the command line)
39
59
  base_url: http://www.jboss.org
60
+
61
+ # Specify the URL to the sitemap to use, rather than the default <base_url>/sitemap.xml
40
62
  sitemap: http://www.jboss.org/my_sitemap.xml
63
+
64
+ # Specify the 'browser' used to load each page from the sitemap. By default
65
+ # typhoeus is used, which will fetch the sources of each page in parallel
66
+ # (fast).
67
+ # Alternatively, you can use phantomjs, which will process the javascript and
68
+ # CSS. This allows any links generated by javascript as well as any resources
69
+ # loaded by the page/javascript to be checked. Additionally, any JS errors are
70
+ # reported. To use phantomjs, you must make sure the native binary is available
71
+ # on your path.
72
+ browser:phantomjs
73
+
74
+ # The number of times to try reloading a link, if the server doesn't respond or
75
+ # refuses the connection. If the retry limit is exceeded, it will be reported as
76
+ # 'Server timed out' in the report. By default 3.
77
+ max_retrys: 3
78
+
79
+ # The number times to try reloading a page. You may want to increase this if you
80
+ # find errors in the console that a page cannot be loaded
81
+ max_page_retrys: 3
82
+
83
+ # Allows blinkr to ignore fragments (#foo) which can reduce the number of URLs
84
+ # to check. By default false.
85
+ ignore_fragments: true
86
+
87
+ # Control the number of threads used to run phantomjs. By default 8.
88
+ phantomjs_threads: 8
89
+
41
90
  ````
42
91
 
43
- Specify a custom config file on the command link:
92
+ You can specify a custom config file on the command link:
44
93
 
45
94
  ````
46
95
  blinkr -c my_blinkr.yaml
47
96
  ````
48
97
 
98
+ If you want to see more details about the URLs blinkr is checking, you can use
99
+ the `-v` option:
100
+
101
+ blinkr -u http://www.jboss.org -v
102
+
103
+ If you need to debug why a particular URL is being reported as bad using
104
+ blinkr, but works in your web browser, you can load a single URL using typhoeus:
105
+
106
+ ````
107
+ blinkr -c my_blinkr.yaml -s http://www.acme.com/corp
108
+ ````
109
+
110
+ Additionally, you can specify the `-w` option to tell libcurl to run in verbose
111
+ mode (this is very verbose, so normally used with `-s`):
112
+
113
+ ````
114
+ blinkr -c my_blinkr.yaml -s http://www.acme.com/corp -v
115
+ ````
116
+
117
+
49
118
  ## Contributing
50
119
 
51
120
  1. Fork it ( http://github.com/pmuir/blinkr/fork )
data/bin/blinkr CHANGED
@@ -14,7 +14,16 @@ OptionParser.new do |opts|
14
14
  opts.on("-u", "--base-url URL", "specify the URL of the site root") do |opt|
15
15
  options[:base_url] = opt
16
16
  end
17
+ opts.on("-v", "--verbose", "output debugging info to the console") do |opt|
18
+ options[:verbose] = opt
19
+ end
20
+ opts.on("-w", "--very-verbose", "additionally, output libcurl debugging info to the console, normally used with -s") do |opt|
21
+ options[:vverbose] = opt
22
+ end
23
+ opts.on("-s", "--single-url URL", "test a single URL, outputting the response to the console") do |opt|
24
+ options[:single_url] = opt
25
+ end
17
26
  end.parse!
18
27
 
19
- Blinkr.run options[:base_url], options[:config]
28
+ Blinkr.run options[:base_url], options[:config], options[:single_url], options[:verbose], options[:vverbose]
20
29
 
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency 'nokogiri', '~> 1.5'
25
25
  spec.add_dependency 'typhoeus', '~> 0.6'
26
26
  spec.add_dependency 'slim', '~> 2.0'
27
+ spec.add_dependency 'parallel', '~> 1.0.0'
27
28
  end
@@ -4,13 +4,18 @@ require 'blinkr/report'
4
4
  require 'yaml'
5
5
 
6
6
  module Blinkr
7
- def self.run(base_url, config = 'blinkr.yaml')
7
+ def self.run(base_url, config = 'blinkr.yaml', single, verbose, vverbose)
8
8
  if !config.nil? && File.exists?(config)
9
9
  config = YAML.load_file(config)
10
10
  else
11
11
  config = {}
12
12
  end
13
- blinkr = Blinkr::Check.new(base_url || config['base_url'], sitemap: config['sitemap'], skips: config['skips'], max_retrys: config['max_retrys'], max_page_retrys: config['max_page_retrys'])
14
- Blinkr::Report.render(blinkr.check, config['report'])
13
+ blinkr = Blinkr::Check.new(base_url || config['base_url'], sitemap: config['sitemap'], skips: config['skips'], max_retrys: config['max_retrys'], max_page_retrys: config['max_page_retrys'], verbose: verbose, vverbose: vverbose, browser: config['browser'], viewport: config['viewport'], ignore_fragments: config['ignore_fragments'], ignores: config['ignores'], phantomjs_threads: config['phantomjs_threads'])
14
+ if single.nil?
15
+ Blinkr::Report.render(blinkr.check, config['report'])
16
+ else
17
+ blinkr.single single
18
+ end
15
19
  end
20
+
16
21
  end
@@ -11,6 +11,10 @@ module Blinkr
11
11
  def set(request, response)
12
12
  @memory[request] = response unless response.timed_out?
13
13
  end
14
+
15
+ def size
16
+ @memory.size
17
+ end
14
18
  end
15
19
  end
16
20
 
@@ -3,11 +3,15 @@ require 'uri'
3
3
  require 'typhoeus'
4
4
  require 'blinkr/cache'
5
5
  require 'ostruct'
6
+ require 'tempfile'
7
+ require 'parallel'
6
8
 
7
9
  module Blinkr
8
10
  class Check
9
11
 
10
- def initialize base_url, sitemap: sitemap, skips: skips, max_retrys: max_retrys, max_page_retrys: max_page_retrys
12
+ SNAP_JS = File.expand_path('snap.js', File.dirname(__FILE__))
13
+
14
+ def initialize base_url, sitemap: '', skips: [], max_retrys: 3, max_page_retrys: 3, verbose: false, vverbose: false, browser: 'typhoeus', viewport: 1200, ignore_fragments: false, ignores: [], phantomjs_threads: 8
11
15
  raise "Must specify base_url" if base_url.nil?
12
16
  unless sitemap.nil?
13
17
  @sitemap = sitemap
@@ -15,83 +19,211 @@ module Blinkr
15
19
  @sitemap = URI.join(base_url, 'sitemap.xml').to_s
16
20
  end
17
21
  @skips = skips || []
22
+ @ignores = ignores || []
23
+ @ignores.each {|ignore| raise "An ignore must be a hash" unless ignore.is_a? Hash}
18
24
  @base_url = base_url
19
25
  @max_retrys = max_retrys || 3
20
26
  @max_page_retrys = max_page_retrys || @max_retrys
21
- Typhoeus::Config.cache = Blinkr::Cache.new
27
+ @browser = browser || 'typhoeus'
28
+ @verbose = vverbose || verbose
29
+ @vverbose = vverbose
30
+ @viewport = viewport || 1200
31
+ @ignore_fragments = ignore_fragments
32
+ Typhoeus::Config.cache = @typhoeus_cache
22
33
  @hydra = Typhoeus::Hydra.new(max_concurrency: 200)
34
+ @phantomjs_count = 0
35
+ @typhoeus_count = 0
36
+ @phantomjs_threads = phantomjs_threads || 8
23
37
  end
24
38
 
25
39
  def check
26
- @errors = {}
40
+ @errors = OpenStruct.new({:links => {}})
41
+ @errors.javascript = {} if @browser == 'phantomjs'
42
+ @errors.resources = {} if @browser == 'phantomjs'
27
43
  puts "Loading sitemap from #{@sitemap}"
28
44
  if @sitemap =~ URI::regexp
29
45
  sitemap = Nokogiri::XML(Typhoeus.get(@sitemap, followlocation: true).body)
30
46
  else
31
47
  sitemap = Nokogiri::XML(File.open(@sitemap))
32
48
  end
33
- sitemap.css('loc').each do |loc|
34
- request = Typhoeus::Request.new(
35
- loc.content,
36
- method: :get,
37
- followlocation: true
38
- )
39
- perform(request, @max_page_retrys) do |resp|
40
- page = Nokogiri::HTML(resp.body)
41
- page.css('a[href]').each do |a|
42
- check_attr(a.attribute('href'), loc.content)
49
+ @links = {}
50
+ pages(sitemap.css('loc').collect { |loc| loc.content }) do |resp|
51
+ if resp.success?
52
+ puts "Loaded page #{resp.request.base_url}" if @verbose
53
+ body = Nokogiri::HTML(resp.body)
54
+ body.css('a[href]').each do |a|
55
+ attr = a.attribute('href')
56
+ src = resp.request.base_url
57
+ url = attr.value
58
+ if !url.nil? && @skips.none? { |regex| regex.match(url) }
59
+ url = sanitize url, src
60
+ @links[url] ||= []
61
+ @links[url] << {:src => src, :line => attr.line, :snippet => attr.parent.to_s}
62
+ end
43
63
  end
44
- page.css('img[src]').each do |img|
45
- check_attr(img.attribute('src'), loc.content)
64
+ else
65
+ puts "#{resp.code} #{resp.status_message} Unable to load page #{resp.request.base_url} #{'(' + resp.return_message + ')' unless resp.return_message.nil?}"
66
+ end
67
+ end
68
+ @hydra.run
69
+ puts "-----------------------------------------------" if @verbose
70
+ puts " Page load complete, #{@links.size} links to check " if @verbose
71
+ puts "-----------------------------------------------" if @verbose
72
+ @links.each do |url, srcs|
73
+ typhoeus(url) do |resp|
74
+ puts "Loaded #{url} via typhoeus #{'(cached)' if resp.cached?}" if @verbose
75
+ unless resp.success? || resp.code == 200
76
+ srcs.each do |src|
77
+ unless ignored? url, resp.code, resp.status_message || resp.return_message
78
+ @errors.links[url] ||= OpenStruct.new({ :code => resp.code.nil? ? nil : resp.code.to_i, :status_message => resp.status_message, :return_message => resp.return_message, :refs => [], :uid => uid(url) })
79
+ @errors.links[url].refs << OpenStruct.new({:src => src[:src], :src_location => "line #{src[:line]}", :snippet => src[:snippet]})
80
+ end
81
+ end
46
82
  end
47
83
  end
48
84
  end
49
85
  @hydra.run
86
+ msg = ''
87
+ msg << "Loaded #{@phantomjs_count} pages using phantomjs." if @phantomjs_count > 0
88
+ msg << "Performed #{@typhoeus_count} requests using typhoeus."
89
+ puts msg
50
90
  @errors
51
91
  end
52
92
 
93
+ def single url
94
+ typhoeus(url) do |resp|
95
+ puts "\n++++++++++"
96
+ puts "+ Blinkr +"
97
+ puts "++++++++++"
98
+ puts "\nRequest"
99
+ puts "======="
100
+ puts "Method: #{resp.request.options[:method]}"
101
+ puts "Max redirects: #{resp.request.options[:maxredirs]}"
102
+ puts "Follow location header: #{resp.request.options[:followlocation]}"
103
+ puts "Timeout (s): #{resp.request.options[:timeout] || 'none'}"
104
+ puts "Connection timeout (s): #{resp.request.options[:connecttimeout] || 'none'}"
105
+ puts "\nHeaders"
106
+ puts "-------"
107
+ unless resp.request.options[:headers].nil?
108
+ resp.request.options[:headers].each do |name, value|
109
+ puts "#{name}: #{value}"
110
+ end
111
+ end
112
+ puts "\nResponse"
113
+ puts "========"
114
+ puts "Status Code: #{resp.code}"
115
+ puts "Status Message: #{resp.status_message}"
116
+ puts "Message: #{resp.return_message}" unless resp.return_message.nil? || resp.return_message == 'No error'
117
+ puts "\nHeaders"
118
+ puts "-------"
119
+ puts resp.response_headers
120
+ end
121
+ @hydra.run
122
+ end
123
+
53
124
  private
54
125
 
55
- def check_attr attr, src
56
- url = attr.value
57
- unless url.nil?
58
- begin
59
- uri = URI(url)
60
- uri = URI.join(src, url) if uri.path.nil? || uri.path.empty? || uri.path.relative?
61
- uri = URI.join(@base_url, uri) if uri.scheme.nil?
62
- url = uri.to_s
63
- rescue Exception => e
126
+ def ignored? url, code, message
127
+ @ignores.any? { |ignore| ( ignore.has_key?('url') ? !ignore['url'].match(url).nil? : true ) && ( ignore.has_key?('code') ? ignore['code'] == code : true ) && ( ignore.has_key?('message') ? !ignore['message'].match(message).nil? : true ) }
128
+ end
129
+
130
+ def sanitize url, src
131
+ begin
132
+ uri = URI(url)
133
+ uri = URI.join(src, url) if uri.path.nil? || uri.path.empty? || uri.relative?
134
+ uri = URI.join(@base_url, uri) if uri.scheme.nil?
135
+ uri.fragment = '' if @ignore_fragments
136
+ url = uri.to_s
137
+ rescue Exception => e
138
+ end
139
+ url.chomp('#').chomp('index.html')
140
+ end
141
+
142
+ def uid url
143
+ url.gsub(/:|\/|\.|\?|#|%|=|&|,|~|;|\!|@|\)|\(|\s/, '_')
144
+ end
145
+
146
+ def pages urls
147
+ if @browser == 'phantomjs'
148
+ Parallel.each(urls, :in_threads => @phantomjs_threads) do |url|
149
+ phantomjs url, @max_page_retrys, &Proc.new
64
150
  end
65
- if uri.nil? || uri.is_a?(URI::HTTP)
66
- request = Typhoeus::Request.new(
67
- url,
68
- method: :head,
69
- followlocation: true
70
- )
71
- perform request do |resp|
72
- unless resp.success?
73
- @errors[request] ||= OpenStruct.new({ :url => url, :code => resp.code.to_i, :message => resp.return_message, :refs => [], :uid => url.gsub(/:|\/|\./, '_') })
74
- @errors[request].refs << OpenStruct.new({:src => src, :line_no => attr.line, :snippet => attr.parent.to_s})
151
+ else
152
+ urls.each do |url|
153
+ typhoeus url, @max_page_retrys, &Proc.new
154
+ end
155
+ end
156
+ end
157
+
158
+ def phantomjs url, limit = @max_retrys, max = -1
159
+ max = limit if max == -1
160
+ if @skips.none? { |regex| regex.match(url) }
161
+ Tempfile.open('blinkr') do|f|
162
+ if system "phantomjs #{SNAP_JS} #{url} #{@viewport} #{f.path}"
163
+ json = JSON.load(File.read(f.path))
164
+ json['resourceErrors'].each do |error|
165
+ start = error['errorString'].rindex('server replied: ')
166
+ errorString = error['errorString'].slice(start.nil? ? 0 : start + 16, error['errorString'].length) unless error['errorString'].nil?
167
+ unless ignored? error['url'], error['errorCode'].nil? ? nil : error['errorCode'].to_i, errorString
168
+ @errors.resources[url] ||= OpenStruct.new({:uid => uid(url), :messages => [] })
169
+ @errors.resources[url].messages << OpenStruct.new(error.update({'errorString' => errorString}))
170
+ end
171
+ end
172
+ json['javascriptErrors'].each do |error|
173
+ @errors.javascript[url] ||= OpenStruct.new({:uid => uid(url), :messages => []})
174
+ @errors.javascript[url].messages << OpenStruct.new(error)
175
+ end
176
+ response = Typhoeus::Response.new(code: 200, body: json['content'], mock: true)
177
+ response.request = Typhoeus::Request.new(url)
178
+ Typhoeus.stub(url).and_return(response)
179
+ yield response
180
+ else
181
+ if limit > 1
182
+ puts "Loading #{url} via phantomjs (attempt #{max - limit + 2} of #{max})" if @verbose
183
+ phantomjs url, limit - 1, max, &Proc.new
184
+ else
185
+ puts "Loading #{url} via phantomjs failed" if @verbose
186
+ response = Typhoeus::Response.new(code: 0, status_message: "Server timed out after #{max} retries", mock: true)
187
+ response.request = Typhoeus::Request.new(url)
188
+ Typhoeus.stub(url).and_return(response)
189
+ yield response
75
190
  end
76
191
  end
77
192
  end
193
+ @phantomjs_count += 1
78
194
  end
79
195
  end
80
196
 
81
- def perform req, limit = @max_retrys
82
- if @skips.none? { |regex| regex.match(req.base_url) }
197
+ def retry? resp
198
+ resp.timed_out? || (resp.code == 0 && [ "Server returned nothing (no headers, no data)", "SSL connect error", "Failure when receiving data from the peer" ].include?(resp.return_message) )
199
+ end
200
+
201
+ def typhoeus url, limit = @max_retrys, max = -1
202
+ max = limit if max == -1
203
+ if @skips.none? { |regex| regex.match(url) }
204
+ req = Typhoeus::Request.new(
205
+ url,
206
+ followlocation: true,
207
+ verbose: @vverbose
208
+ )
83
209
  req.on_complete do |resp|
84
- if resp.timed_out?
85
- if limit > 0
86
- perform(Typhoeus::Request.new(req.base_url, req.options), limit - 1, &Proc.new)
210
+ if retry? resp
211
+ if limit > 1
212
+ puts "Loading #{url} via typhoeus (attempt #{max - limit + 2} of #{max})" if @verbose
213
+ typhoeus(url, limit - 1, max, &Proc.new)
87
214
  else
88
- yield OpenStruct.new({:success => false, :code => '0', :return_message => "Server timed out"})
215
+ puts "Loading #{url} via typhoeus failed" if @verbose
216
+ response = Typhoeus::Response.new(code: 0, status_message: "Server timed out after #{max} retries", mock: true)
217
+ response.request = Typhoeus::Request.new(url)
218
+ Typhoeus.stub(url).and_return(response)
219
+ yield response
89
220
  end
90
221
  else
91
222
  yield resp
92
223
  end
93
224
  end
94
225
  @hydra.queue req
226
+ @typhoeus_count += 1
95
227
  end
96
228
  end
97
229
  end
@@ -10,39 +10,140 @@ body
10
10
 
11
11
  .container
12
12
  h1 Blinkr
13
- p.lead
14
- | Detecting broken links
15
- - errors.each do |req, err|
13
+ ol
14
+ li
15
+ a href='#links'
16
+ p class="#{errors.links.empty? ? 'text-success' : 'text-danger'}"
17
+ |Broken links
18
+ - unless errors.javascript.nil?
19
+ li
20
+ a href='#javascript'
21
+ p class="#{errors.javascript.empty? ? 'text-success' : 'text-danger'}"
22
+ | Page load JavaScript errors
23
+ - unless errors.resources.nil?
24
+ li
25
+ a href='#resources'
26
+ p class="#{errors.resources.empty? ? 'text-success' : 'text-danger'}"
27
+ | Resource loading errors
28
+
29
+ a id='links'
30
+ h2
31
+ |Broken links (
32
+ code
33
+ |a[href]
34
+ |)
35
+ - errors.links.each do |url, err|
16
36
  .panel.panel-danger
17
37
  .panel-heading
18
- a data-toggle="collapse" href="##{err.uid}" id="control-#{err.uid}"
19
- i.fa.fa-plus-square
38
+ a data-toggle="collapse" href="#a-#{err.uid}" id="control-a-#{err.uid}"
39
+ i.fa.fa-caret-square-o-right
20
40
  '
21
- a href="#{req.base_url}" target='_blank'
22
- | #{req.base_url}
41
+ a href="#{url}" target='_blank'
42
+ | #{url}
23
43
  i.fa.fa-external-link
24
44
  .pull-right.badge
25
- | #{err.code if err.code > 0} #{err.message if err.message != 'No error'}
26
- .panel-body.collapse id="#{err.uid}"
45
+ - if err.code > 0 || !err.status_message.nil?
46
+ span data-toggle="tooltip" title="Status message"
47
+ ' #{err.code if err.code > 0} #{err.status_message}
48
+ - elsif !err.return_message.nil? && err.return_message != 'No error'
49
+ span data-toggle="tooltip" title="Return message"
50
+ ' #{err.return_message}
51
+ .panel-body.collapse id="a-#{err.uid}"
52
+ - unless err.return_message.nil? || err.return_message == 'No error' || (err.code == 0 && err.status_message.nil?)
53
+ |Message: #{err.return_message}
27
54
  ul.list-group
28
55
  - err.refs.each do |ref|
29
56
  li.list-group-item
30
- '#{ref.src} (line #{ref.line_no})
57
+ '#{ref.src}
31
58
  a href="#{ref.src}" target='_blank'
32
59
  i.fa.fa-external-link
33
60
  '
34
61
  a href="view-source:#{ref.src}" target='_blank'
35
62
  i.fa.fa-file-code-o
63
+ .pull-right
64
+ |(#{ref.src_location})
36
65
  pre #{ref.snippet}
37
66
  javascript:
38
- $('##{err.uid}').on('show.bs.collapse', function () {
39
- $('a#control-#{err.uid}').html('<i class="fa fa-minus-square"></i>');
67
+ $('#a-#{err.uid}').on('show.bs.collapse', function () {
68
+ $('a#control-a-#{err.uid}').html('<i class="fa fa-caret-square-o-down"></i>');
40
69
  });
41
- $('##{err.uid}').on('hide.bs.collapse', function () {
42
- $('a#control-#{err.uid}').html('<i class="fa fa-plus-square"></i>');
70
+ $('#a-#{err.uid}').on('hide.bs.collapse', function () {
71
+ $('a#control-a-#{err.uid}').html('<i class="fa fa-caret-square-o-right"></i>');
43
72
  });
44
- - if errors.empty?
73
+ - if errors.links.empty?
45
74
  .panel.panel-success
46
75
  .panel-heading
76
+ a id='resources'
47
77
  | No broken links detected
78
+ - unless errors.javascript.nil?
79
+ a id='javascript'
80
+ h2 Page load JavaScript errors
81
+ - errors.javascript.each do |url, err|
82
+ .panel.panel-danger
83
+ .panel-heading
84
+ a data-toggle="collapse" href="#j-#{err.uid}" id="control-j-#{err.uid}"
85
+ i.fa.fa-caret-square-o-right
86
+ '
87
+ a href="#{url}" target='_blank'
88
+ | #{url}
89
+ i.fa.fa-external-link
90
+ .panel-body.collapse id="j-#{err.uid}"
91
+ ul.list-group
92
+ - err.messages.each do |message|
93
+ li.list-group-item
94
+ pre= message.msg
95
+ pre= message.trace
96
+ javascript:
97
+ $('#j-#{err.uid}').on('show.bs.collapse', function () {
98
+ $('a#control-j-#{err.uid}').html('<i class="fa fa-caret-square-o-down"></i>');
99
+ });
100
+ $('#j-#{err.uid}').on('hide.bs.collapse', function () {
101
+ $('a#control-j-#{err.uid}').html('<i class="fa fa-caret-square-o-right"></i>');
102
+ });
103
+ - if errors.javascript.empty?
104
+ .panel.panel-success
105
+ .panel-heading
106
+ a id='javascript'
107
+ | No JavaScript errors detected
108
+
109
+ - unless errors.resources.nil?
110
+ a id='resources'
111
+ h2 Resource loading errors
112
+ - errors.resources.each do |url, err|
113
+ .panel.panel-danger
114
+ .panel-heading
115
+ a data-toggle="collapse" href="#r-#{err.uid}" id="control-r-#{err.uid}"
116
+ i.fa.fa-caret-square-o-right
117
+ '
118
+ a href="#{url}" target='_blank'
119
+ | #{url}
120
+ i.fa.fa-external-link
121
+ .panel-body.collapse id="r-#{err.uid}"
122
+ ul.list-group
123
+ - err.messages.each do |message|
124
+ li.list-group-item
125
+ '#{message.url}
126
+ a href="#{message.url}" target='_blank'
127
+ i.fa.fa-external-link
128
+ .pull-right.badge.alert-danger
129
+ - if message.errorCode > 0
130
+ ' #{message.errorCode}
131
+ - unless message.errorString.nil?
132
+ | #{message.errorString}
133
+
134
+ javascript:
135
+ $('#r-#{err.uid}').on('show.bs.collapse', function () {
136
+ $('a#control-r-#{err.uid}').html('<i class="fa fa-caret-square-o-down"></i>');
137
+ });
138
+ $('#r-#{err.uid}').on('hide.bs.collapse', function () {
139
+ $('a#control-r-#{err.uid}').html('<i class="fa fa-caret-square-o-right"></i>');
140
+ });
141
+ - if errors.resources.empty?
142
+ .panel.panel-success
143
+ .panel-heading
144
+ a id='resources'
145
+ | No resource loading errors detected
146
+
147
+ javascript:
148
+ $('[data-toggle="tooltip"]').tooltip({'placement': 'top'});
48
149
 
@@ -0,0 +1,99 @@
1
+ var system = require('system');
2
+ var page = require('webpage').create();
3
+ var fs = require('fs');
4
+
5
+ if (system.args.length === 3) {
6
+ console.log('Usage: snap.js <URL> <view port width> <out>');
7
+ phantom.exit();
8
+ }
9
+
10
+ var info = {};
11
+ info.resourceErrors = [];
12
+ info.javascriptErrors = [];
13
+ info.url = system.args[1];
14
+ info.view_port_width = system.args[2];
15
+
16
+ var out = system.args[3]
17
+ var current_requests = 0;
18
+ var last_request_timeout;
19
+ var final_timeout;
20
+
21
+
22
+ page.viewportSize = { width: info.view_port_width, height: 1500};
23
+ page.settings = { loadImages: true, javascriptEnabled: true };
24
+
25
+ // If you want to use additional phantomjs commands, place them here
26
+ page.settings.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.17';
27
+
28
+ // You can place custom headers here, example below.
29
+ // page.customHeaders = {
30
+
31
+ // 'X-Candy-OVERRIDE': 'https://api.live.bbc.co.uk/'
32
+
33
+ // };
34
+
35
+ // If you want to set a cookie, just add your details below in the following way.
36
+
37
+ // phantom.addCookie({
38
+ // 'name': 'ckns_policy',
39
+ // 'value': '111',
40
+ // 'domain': '.bbc.co.uk'
41
+ // });
42
+ // phantom.addCookie({
43
+ // 'name': 'locserv',
44
+ // 'value': '1#l1#i=6691484:n=Oxford+Circus:h=e@w1#i=8:p=London@d1#1=l:2=e:3=e:4=2@n1#r=40',
45
+ // 'domain': '.bbc.co.uk'
46
+ // });
47
+
48
+ page.onResourceRequested = function(req) {
49
+ current_requests += 1;
50
+ };
51
+
52
+ page.onResourceReceived = function(resp) {
53
+ if (resp.stage === 'end') {
54
+ current_requests -= 1;
55
+ }
56
+ timeout();
57
+ };
58
+
59
+ page.onResourceError = function(metadata) {
60
+ info.resourceErrors[info.resourceErrors.length] = metadata;
61
+ }
62
+
63
+
64
+ page.onError = function(msg, trace) {
65
+ info.javascriptErrors[info.javascriptErrors.length] = {msg: msg, trace: trace};
66
+ }
67
+
68
+ page.open(info.url, function(status) {
69
+ if (status !== 'success') {
70
+ exit(1);
71
+ }
72
+ });
73
+
74
+ var exit = function exit(err_code) {
75
+ info.content = page.content;
76
+ fs.write(out, JSON.stringify(info), 'w');
77
+ if (err_code === undefined) {
78
+ err_code = 0;
79
+ }
80
+ phantom.exit(err_code);
81
+ };
82
+
83
+ function timeout() {
84
+ clearTimeout(last_request_timeout);
85
+ clearTimeout(final_timeout);
86
+
87
+ // If there's no more ongoing resource requests, wait for 1 second before
88
+ // exiting, just in case the page kicks off another request
89
+ if (current_requests < 1) {
90
+ clearTimeout(final_timeout);
91
+ last_request_timeout = setTimeout(exit, 1000);
92
+ }
93
+
94
+ // Sometimes, straggling requests never make it back, in which
95
+ // case, timeout after 5 seconds and exit anyway
96
+ // TODO record which requests timed out!
97
+ final_timeout = setTimeout(exit, 5000);
98
+ }
99
+
@@ -1,4 +1,4 @@
1
1
  module Blinkr
2
- VERSION='0.0.2'
2
+ VERSION='0.1.0'
3
3
  end
4
4
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blinkr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pete Muir
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-11 00:00:00.000000000 Z
11
+ date: 2014-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: parallel
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.0.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.0.0
83
97
  description:
84
98
  email:
85
99
  - pmuir@bleepbleep.org.uk
@@ -102,6 +116,7 @@ files:
102
116
  - lib/blinkr/check.rb
103
117
  - lib/blinkr/report.html.slim
104
118
  - lib/blinkr/report.rb
119
+ - lib/blinkr/snap.js
105
120
  - lib/blinkr/version.rb
106
121
  homepage: ''
107
122
  licenses: