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 +4 -4
- data/README.md +74 -5
- data/bin/blinkr +10 -1
- data/blinkr.gemspec +1 -0
- data/lib/blinkr.rb +8 -3
- data/lib/blinkr/cache.rb +4 -0
- data/lib/blinkr/check.rb +172 -40
- data/lib/blinkr/report.html.slim +116 -15
- data/lib/blinkr/snap.js +99 -0
- data/lib/blinkr/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8e918082f460b113ad7517dc20f4838c1b3052d
|
4
|
+
data.tar.gz: 523df75b0eb689beb7ce691371c4e0fc8eb422c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/blinkr.gemspec
CHANGED
data/lib/blinkr.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/blinkr/cache.rb
CHANGED
data/lib/blinkr/check.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
45
|
-
|
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
|
56
|
-
url
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
82
|
-
|
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
|
85
|
-
if limit >
|
86
|
-
|
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
|
-
|
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
|
data/lib/blinkr/report.html.slim
CHANGED
@@ -10,39 +10,140 @@ body
|
|
10
10
|
|
11
11
|
.container
|
12
12
|
h1 Blinkr
|
13
|
-
|
14
|
-
|
15
|
-
|
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="
|
19
|
-
i.fa.fa-
|
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="#{
|
22
|
-
| #{
|
41
|
+
a href="#{url}" target='_blank'
|
42
|
+
| #{url}
|
23
43
|
i.fa.fa-external-link
|
24
44
|
.pull-right.badge
|
25
|
-
|
26
|
-
|
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}
|
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
|
-
$('
|
39
|
-
$('a#control-#{err.uid}').html('<i class="fa fa-
|
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
|
-
$('
|
42
|
-
$('a#control-#{err.uid}').html('<i class="fa fa-
|
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
|
|
data/lib/blinkr/snap.js
ADDED
@@ -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
|
+
|
data/lib/blinkr/version.rb
CHANGED
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
|
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
|
+
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:
|