blinkr 0.0.2 → 0.1.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: 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: