autobench 0.0.1alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +16 -0
- data/README.md +123 -0
- data/bin/autobench +180 -0
- data/bin/autobench-config +162 -0
- data/lib/autobench.rb +28 -0
- data/lib/autobench/client.rb +78 -0
- data/lib/autobench/common.rb +49 -0
- data/lib/autobench/config.rb +62 -0
- data/lib/autobench/render.rb +102 -0
- data/lib/autobench/version.rb +3 -0
- data/lib/autobench/yslow.rb +75 -0
- data/lib/phantomas/README.md +296 -0
- data/lib/phantomas/core/formatter.js +65 -0
- data/lib/phantomas/core/helper.js +64 -0
- data/lib/phantomas/core/modules/requestsMonitor/requestsMonitor.js +214 -0
- data/lib/phantomas/core/pads.js +16 -0
- data/lib/phantomas/core/phantomas.js +418 -0
- data/lib/phantomas/lib/args.js +27 -0
- data/lib/phantomas/lib/modules/_coffee-script.js +2 -0
- data/lib/phantomas/lib/modules/assert.js +326 -0
- data/lib/phantomas/lib/modules/events.js +216 -0
- data/lib/phantomas/lib/modules/http.js +55 -0
- data/lib/phantomas/lib/modules/path.js +441 -0
- data/lib/phantomas/lib/modules/punycode.js +510 -0
- data/lib/phantomas/lib/modules/querystring.js +214 -0
- data/lib/phantomas/lib/modules/tty.js +7 -0
- data/lib/phantomas/lib/modules/url.js +625 -0
- data/lib/phantomas/lib/modules/util.js +520 -0
- data/lib/phantomas/modules/ajaxRequests/ajaxRequests.js +15 -0
- data/lib/phantomas/modules/assetsTypes/assetsTypes.js +21 -0
- data/lib/phantomas/modules/cacheHits/cacheHits.js +28 -0
- data/lib/phantomas/modules/caching/caching.js +66 -0
- data/lib/phantomas/modules/cookies/cookies.js +54 -0
- data/lib/phantomas/modules/domComplexity/domComplexity.js +130 -0
- data/lib/phantomas/modules/domQueries/domQueries.js +148 -0
- data/lib/phantomas/modules/domains/domains.js +49 -0
- data/lib/phantomas/modules/globalVariables/globalVariables.js +44 -0
- data/lib/phantomas/modules/headers/headers.js +48 -0
- data/lib/phantomas/modules/localStorage/localStorage.js +14 -0
- data/lib/phantomas/modules/requestsStats/requestsStats.js +71 -0
- data/lib/phantomas/modules/staticAssets/staticAssets.js +40 -0
- data/lib/phantomas/modules/waterfall/waterfall.js +62 -0
- data/lib/phantomas/modules/windowPerformance/windowPerformance.js +36 -0
- data/lib/phantomas/package.json +27 -0
- data/lib/phantomas/phantomas.js +35 -0
- data/lib/phantomas/run-multiple.js +177 -0
- data/lib/yslow.js +5 -0
- metadata +135 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
/**
|
2
|
+
* Results formatter
|
3
|
+
*/
|
4
|
+
var formatter = function(results, format) {
|
5
|
+
function render() {
|
6
|
+
switch(format) {
|
7
|
+
case 'json':
|
8
|
+
return formatJson();
|
9
|
+
|
10
|
+
case 'csv':
|
11
|
+
return formatCsv();
|
12
|
+
|
13
|
+
case 'plain':
|
14
|
+
default:
|
15
|
+
return formatPlain();
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
function formatJson() {
|
20
|
+
return JSON.stringify(results);
|
21
|
+
}
|
22
|
+
|
23
|
+
function formatCsv() {
|
24
|
+
var obj = results.metrics,
|
25
|
+
key,
|
26
|
+
keys = [],
|
27
|
+
values = [];
|
28
|
+
|
29
|
+
for (key in obj) {
|
30
|
+
keys.push(key);
|
31
|
+
values.push(obj[key]);
|
32
|
+
}
|
33
|
+
|
34
|
+
return keys.join(',') + "\n" + values.join(',');
|
35
|
+
}
|
36
|
+
|
37
|
+
function formatPlain() {
|
38
|
+
var res = '',
|
39
|
+
obj = results.metrics,
|
40
|
+
key;
|
41
|
+
|
42
|
+
// header
|
43
|
+
res += 'phantomas metrics for <' + results.url + '>:\n\n';
|
44
|
+
|
45
|
+
// metrics
|
46
|
+
for (key in obj) {
|
47
|
+
res += '* ' + key + ': ' + obj[key]+ '\n';
|
48
|
+
}
|
49
|
+
|
50
|
+
res += '\n';
|
51
|
+
|
52
|
+
// notices
|
53
|
+
results.notices.forEach(function(msg) {
|
54
|
+
res += '> ' + msg + "\n";
|
55
|
+
});
|
56
|
+
|
57
|
+
return res.trim();
|
58
|
+
}
|
59
|
+
|
60
|
+
// public interface
|
61
|
+
this.render = render;
|
62
|
+
};
|
63
|
+
|
64
|
+
exports.formatter = formatter;
|
65
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
/**
|
2
|
+
* phantomas helper code
|
3
|
+
*
|
4
|
+
* Executed in page window
|
5
|
+
*/
|
6
|
+
|
7
|
+
(function(window) {
|
8
|
+
|
9
|
+
// NodeRunner
|
10
|
+
var nodeRunner = function() {
|
11
|
+
// "Beep, Beep"
|
12
|
+
};
|
13
|
+
|
14
|
+
nodeRunner.prototype = {
|
15
|
+
// call callback for each child of node
|
16
|
+
walk: function(node, callback, depth) {
|
17
|
+
if (this.isSkipped(node)) {
|
18
|
+
return;
|
19
|
+
}
|
20
|
+
|
21
|
+
var childNode,
|
22
|
+
childNodes = node.childNodes || [];
|
23
|
+
|
24
|
+
depth = (depth || 1);
|
25
|
+
|
26
|
+
for (var n=0, len = childNodes.length; n < len; n++) {
|
27
|
+
childNode = childNodes[n];
|
28
|
+
|
29
|
+
// callback can return false to stop recursive
|
30
|
+
if (callback(childNode, depth) !== false) {
|
31
|
+
this.walk(childNode, callback, depth + 1);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
},
|
35
|
+
|
36
|
+
// override this function when you create an object of class phantomas.nodeRunner
|
37
|
+
// by default only iterate over HTML elements
|
38
|
+
isSkipped: function(node) {
|
39
|
+
return !node || (node.nodeType !== Node.ELEMENT_NODE);
|
40
|
+
}
|
41
|
+
};
|
42
|
+
|
43
|
+
function getCaller() {
|
44
|
+
var caller = {};
|
45
|
+
|
46
|
+
try {
|
47
|
+
throw new Error('backtrace');
|
48
|
+
} catch(e) {
|
49
|
+
caller = (e.stackArray && e.stackArray[3]) || {};
|
50
|
+
}
|
51
|
+
|
52
|
+
return caller;
|
53
|
+
}
|
54
|
+
|
55
|
+
// create a scope
|
56
|
+
var phantomas = (window.phantomas = window.phantomas || {});
|
57
|
+
|
58
|
+
// exports
|
59
|
+
phantomas.nodeRunner = nodeRunner;
|
60
|
+
phantomas.getCaller = getCaller;
|
61
|
+
|
62
|
+
console.log('phantomas scope injected');
|
63
|
+
|
64
|
+
})(window);
|
@@ -0,0 +1,214 @@
|
|
1
|
+
/**
|
2
|
+
* Simple HTTP requests monitor and analyzer
|
3
|
+
*/
|
4
|
+
exports.version = '1.1';
|
5
|
+
|
6
|
+
exports.module = function(phantomas) {
|
7
|
+
// imports
|
8
|
+
var HTTP_STATUS_CODES = phantomas.require('http').STATUS_CODES,
|
9
|
+
parseUrl = phantomas.require('url').parse;
|
10
|
+
|
11
|
+
var requests = [];
|
12
|
+
|
13
|
+
// register metric
|
14
|
+
phantomas.setMetric('requests');
|
15
|
+
phantomas.setMetric('gzipRequests');
|
16
|
+
phantomas.setMetric('postRequests');
|
17
|
+
phantomas.setMetric('redirects');
|
18
|
+
phantomas.setMetric('notFound');
|
19
|
+
phantomas.setMetric('timeToFirstByte');
|
20
|
+
phantomas.setMetric('timeToLastByte');
|
21
|
+
phantomas.setMetric('bodySize'); // content only
|
22
|
+
phantomas.setMetric('contentLength'); // content only
|
23
|
+
|
24
|
+
// parse given URL to get protocol and domain
|
25
|
+
function parseEntryUrl(entry) {
|
26
|
+
var parsed;
|
27
|
+
|
28
|
+
if (entry.url.indexOf('data:') !== 0) {
|
29
|
+
// @see http://nodejs.org/api/url.html#url_url
|
30
|
+
parsed = parseUrl(entry.url) || {};
|
31
|
+
|
32
|
+
entry.protocol = parsed.protocol.replace(':', '');
|
33
|
+
entry.domain = parsed.hostname;
|
34
|
+
entry.query = parsed.query;
|
35
|
+
|
36
|
+
if (entry.protocol === 'https') {
|
37
|
+
entry.isSSL = true;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
else {
|
41
|
+
// base64 encoded data
|
42
|
+
entry.domain = false;
|
43
|
+
entry.protocol = false;
|
44
|
+
entry.isBase64 = true;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
// when the monitoring started?
|
49
|
+
var start;
|
50
|
+
phantomas.on('pageOpen', function(res) {
|
51
|
+
start = Date.now();
|
52
|
+
});
|
53
|
+
|
54
|
+
phantomas.on('onResourceRequested', function(res) {
|
55
|
+
// store current request data
|
56
|
+
var entry = requests[res.id] = {
|
57
|
+
id: res.id,
|
58
|
+
url: res.url,
|
59
|
+
method: res.method,
|
60
|
+
requestHeaders: {},
|
61
|
+
sendTime: res.time,
|
62
|
+
bodySize: 0
|
63
|
+
};
|
64
|
+
|
65
|
+
res.headers.forEach(function(header) {
|
66
|
+
entry.requestHeaders[header.name] = header.value;
|
67
|
+
});
|
68
|
+
|
69
|
+
parseEntryUrl(entry);
|
70
|
+
|
71
|
+
if (!entry.isBase64) {
|
72
|
+
phantomas.emit('send', entry, res);
|
73
|
+
}
|
74
|
+
});
|
75
|
+
|
76
|
+
phantomas.on('onResourceReceived', function(res) {
|
77
|
+
// current request data
|
78
|
+
var entry = requests[res.id];
|
79
|
+
|
80
|
+
switch(res.stage) {
|
81
|
+
// the beginning of response
|
82
|
+
case 'start':
|
83
|
+
entry.recvStartTime = res.time;
|
84
|
+
entry.timeToFirstByte = res.time - entry.sendTime;
|
85
|
+
|
86
|
+
// FIXME: buggy
|
87
|
+
// @see http://code.google.com/p/phantomjs/issues/detail?id=169
|
88
|
+
entry.bodySize += res.bodySize || 0;
|
89
|
+
break;
|
90
|
+
|
91
|
+
// the end of response
|
92
|
+
case 'end':
|
93
|
+
// timing
|
94
|
+
entry.recvEndTime = res.time;
|
95
|
+
entry.timeToLastByte = res.time - entry.sendTime;
|
96
|
+
entry.receiveTime = entry.recvEndTime - entry.recvStartTime;
|
97
|
+
|
98
|
+
// request method
|
99
|
+
switch(entry.method) {
|
100
|
+
case 'POST':
|
101
|
+
phantomas.incrMetric('postRequests');
|
102
|
+
break;
|
103
|
+
}
|
104
|
+
|
105
|
+
// HTTP code
|
106
|
+
entry.status = res.status || 200 /* for base64 data */;
|
107
|
+
entry.statusText = HTTP_STATUS_CODES[entry.status];
|
108
|
+
|
109
|
+
switch(entry.status) {
|
110
|
+
case 301:
|
111
|
+
case 302:
|
112
|
+
phantomas.incrMetric('redirects');
|
113
|
+
phantomas.addNotice(entry.url + ' is a redirect (HTTP ' + entry.status + ')');
|
114
|
+
break;
|
115
|
+
|
116
|
+
case 404:
|
117
|
+
phantomas.incrMetric('notFound');
|
118
|
+
phantomas.addNotice(entry.url + ' was not found (HTTP 404)');
|
119
|
+
break;
|
120
|
+
}
|
121
|
+
|
122
|
+
parseEntryUrl(entry);
|
123
|
+
|
124
|
+
// asset type
|
125
|
+
entry.type = 'other';
|
126
|
+
|
127
|
+
// analyze HTTP headers
|
128
|
+
entry.headers = {};
|
129
|
+
res.headers.forEach(function(header) {
|
130
|
+
entry.headers[header.name] = header.value;
|
131
|
+
|
132
|
+
switch (header.name.toLowerCase()) {
|
133
|
+
// TODO: why it's not gzipped?
|
134
|
+
// because: http://code.google.com/p/phantomjs/issues/detail?id=156
|
135
|
+
// should equal bodySize
|
136
|
+
case 'content-length':
|
137
|
+
entry.contentLength = parseInt(header.value, 10);
|
138
|
+
break;
|
139
|
+
|
140
|
+
// detect content type
|
141
|
+
case 'content-type':
|
142
|
+
// parse header value
|
143
|
+
var value = header.value.split(';').shift().toLowerCase();
|
144
|
+
|
145
|
+
switch(value) {
|
146
|
+
case 'text/html':
|
147
|
+
entry.type = 'html';
|
148
|
+
entry.isHTML = true;
|
149
|
+
break;
|
150
|
+
|
151
|
+
case 'text/css':
|
152
|
+
entry.type = 'css';
|
153
|
+
entry.isCSS = true;
|
154
|
+
break;
|
155
|
+
|
156
|
+
case 'application/x-javascript':
|
157
|
+
case 'application/javascript':
|
158
|
+
case 'text/javascript':
|
159
|
+
entry.type = 'js';
|
160
|
+
entry.isJS = true;
|
161
|
+
break;
|
162
|
+
|
163
|
+
case 'image/png':
|
164
|
+
case 'image/jpeg':
|
165
|
+
case 'image/gif':
|
166
|
+
entry.type = 'image';
|
167
|
+
entry.isImage = true;
|
168
|
+
break;
|
169
|
+
|
170
|
+
default:
|
171
|
+
phantomas.addNotice('Unknown content type found: ' + value);
|
172
|
+
}
|
173
|
+
break;
|
174
|
+
|
175
|
+
// detect content encoding
|
176
|
+
case 'content-encoding':
|
177
|
+
if (header.value === 'gzip') {
|
178
|
+
entry.gzip = true;
|
179
|
+
}
|
180
|
+
break;
|
181
|
+
}
|
182
|
+
});
|
183
|
+
|
184
|
+
// requests stats
|
185
|
+
if (!entry.isBase64) {
|
186
|
+
phantomas.incrMetric('requests');
|
187
|
+
|
188
|
+
phantomas.incrMetric('bodySize', entry.bodySize); // content only
|
189
|
+
phantomas.incrMetric('contentLength', entry.contentLength || entry.bodySize); // content only
|
190
|
+
}
|
191
|
+
|
192
|
+
if (entry.gzip) {
|
193
|
+
phantomas.incrMetric('gzipRequests');
|
194
|
+
}
|
195
|
+
|
196
|
+
// emit an event for other modules
|
197
|
+
phantomas.emit(entry.isBase64 ? 'base64recv' : 'recv' , entry, res);
|
198
|
+
//phantomas.log(entry);
|
199
|
+
break;
|
200
|
+
}
|
201
|
+
});
|
202
|
+
|
203
|
+
// TTFB / TTLB metrics
|
204
|
+
phantomas.on('recv', function(entry, res) {
|
205
|
+
// check the first request
|
206
|
+
if (entry.id === 1) {
|
207
|
+
phantomas.setMetric('timeToFirstByte', entry.timeToFirstByte);
|
208
|
+
phantomas.setMetric('timeToLastByte', entry.timeToLastByte);
|
209
|
+
}
|
210
|
+
|
211
|
+
// completion of the last HTTP request
|
212
|
+
phantomas.setMetric('httpTrafficCompleted', entry.recvEndTime - start);
|
213
|
+
});
|
214
|
+
};
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/**
|
2
|
+
* Helper functions for string formatting
|
3
|
+
*/
|
4
|
+
function lpad(str, len) {
|
5
|
+
var fill = new Array( Math.max(1, len - str.toString().length + 1) ).join(' ');
|
6
|
+
return fill + str;
|
7
|
+
}
|
8
|
+
|
9
|
+
function rpad(str, len) {
|
10
|
+
var fill = new Array( Math.max(1, len - str.toString().length + 1) ).join(' ');
|
11
|
+
return str + fill;
|
12
|
+
}
|
13
|
+
|
14
|
+
exports.lpad = lpad;
|
15
|
+
exports.rpad = rpad;
|
16
|
+
|
@@ -0,0 +1,418 @@
|
|
1
|
+
/**
|
2
|
+
* phantomas main file
|
3
|
+
*/
|
4
|
+
|
5
|
+
var VERSION = '0.4.1';
|
6
|
+
|
7
|
+
var getDefaultUserAgent = function() {
|
8
|
+
var version = phantom.version,
|
9
|
+
system = require('system'),
|
10
|
+
os = system.os;
|
11
|
+
|
12
|
+
return "phantomas/" + VERSION + " (PhantomJS/" + version.major + "." + version.minor + "." + version.patch + "; " + os.name + " " + os.architecture + ")";
|
13
|
+
}
|
14
|
+
|
15
|
+
var phantomas = function(params) {
|
16
|
+
// parse script CLI parameters
|
17
|
+
this.params = params;
|
18
|
+
|
19
|
+
// --url=http://example.com
|
20
|
+
this.url = this.params.url;
|
21
|
+
|
22
|
+
// --format=[csv|json]
|
23
|
+
this.resultsFormat = params.format || 'plain';
|
24
|
+
|
25
|
+
// --viewport=1280x1024
|
26
|
+
this.viewport = params.viewport || '1280x1024';
|
27
|
+
|
28
|
+
// --verbose
|
29
|
+
this.verboseMode = params.verbose === true;
|
30
|
+
|
31
|
+
// --silent
|
32
|
+
this.silentMode = params.silent === true;
|
33
|
+
|
34
|
+
// --timeout (in seconds)
|
35
|
+
this.timeout = (params.timeout > 0 && parseInt(params.timeout, 10)) || 15;
|
36
|
+
|
37
|
+
// --modules=localStorage,cookies
|
38
|
+
this.modules = (params.modules) ? params.modules.split(',') : [];
|
39
|
+
|
40
|
+
// --user-agent=custom-agent
|
41
|
+
this.userAgent = params['user-agent'] || getDefaultUserAgent();
|
42
|
+
|
43
|
+
// setup the stuff
|
44
|
+
this.emitter = new (this.require('events').EventEmitter)();
|
45
|
+
this.emitter.setMaxListeners(200);
|
46
|
+
|
47
|
+
this.page = require('webpage').create();
|
48
|
+
|
49
|
+
// current HTTP requests counter
|
50
|
+
this.currentRequests = 0;
|
51
|
+
|
52
|
+
this.log('phantomas v' + VERSION);
|
53
|
+
|
54
|
+
// load core modules
|
55
|
+
this.addCoreModule('requestsMonitor');
|
56
|
+
|
57
|
+
// load 3rd party modules
|
58
|
+
var modules = (this.modules.length > 0) ? this.modules : this.listModules(),
|
59
|
+
self = this;
|
60
|
+
|
61
|
+
modules.forEach(function(moduleName) {
|
62
|
+
self.addModule(moduleName);
|
63
|
+
});
|
64
|
+
};
|
65
|
+
|
66
|
+
phantomas.version = VERSION;
|
67
|
+
|
68
|
+
phantomas.prototype = {
|
69
|
+
metrics: {},
|
70
|
+
notices: [],
|
71
|
+
|
72
|
+
// simple version of jQuery.proxy
|
73
|
+
proxy: function(fn, scope) {
|
74
|
+
scope = scope || this;
|
75
|
+
return function () {
|
76
|
+
return fn.apply(scope, arguments);
|
77
|
+
};
|
78
|
+
},
|
79
|
+
|
80
|
+
// emit given event
|
81
|
+
emit: function(/* eventName, arg1, arg2, ... */) {
|
82
|
+
this.log('Event ' + arguments[0] + ' emitted');
|
83
|
+
this.emitter.emit.apply(this.emitter, arguments);
|
84
|
+
},
|
85
|
+
|
86
|
+
// bind to a given event
|
87
|
+
on: function(ev, fn) {
|
88
|
+
this.emitter.on(ev, fn);
|
89
|
+
},
|
90
|
+
|
91
|
+
once: function(ev, fn) {
|
92
|
+
this.emitter.once(ev, fn);
|
93
|
+
},
|
94
|
+
|
95
|
+
// returns "wrapped" version of phantomas object with public methods / fields only
|
96
|
+
getPublicWrapper: function() {
|
97
|
+
var self = this;
|
98
|
+
|
99
|
+
// modules API
|
100
|
+
return {
|
101
|
+
url: this.params.url,
|
102
|
+
params: this.params,
|
103
|
+
|
104
|
+
// events
|
105
|
+
on: function() {self.on.apply(self, arguments);},
|
106
|
+
once: function() {self.once.apply(self, arguments);},
|
107
|
+
emit: function() {self.emit.apply(self, arguments);},
|
108
|
+
|
109
|
+
// metrics
|
110
|
+
setMetric: function() {self.setMetric.apply(self, arguments);},
|
111
|
+
setMetricEvaluate: function() {self.setMetricEvaluate.apply(self, arguments);},
|
112
|
+
incrMetric: function() {self.incrMetric.apply(self, arguments);},
|
113
|
+
|
114
|
+
// debug
|
115
|
+
addNotice: function(msg) {self.addNotice(msg);},
|
116
|
+
log: function(msg) {self.log(msg);},
|
117
|
+
echo: function(msg) {self.echo(msg);},
|
118
|
+
|
119
|
+
// phantomJS
|
120
|
+
evaluate: function(fn) {return self.page.evaluate(fn);},
|
121
|
+
injectJs: function(src) {return self.page.injectJs(src);},
|
122
|
+
require: function(module) {return self.require(module);},
|
123
|
+
getPageContent: function() {return self.page.content;}
|
124
|
+
};
|
125
|
+
},
|
126
|
+
|
127
|
+
// initialize given core phantomas module
|
128
|
+
addCoreModule: function(name) {
|
129
|
+
var pkg = require('./modules/' + name + '/' + name);
|
130
|
+
|
131
|
+
// init a module
|
132
|
+
pkg.module(this.getPublicWrapper());
|
133
|
+
|
134
|
+
this.log('Core module ' + name + (pkg.version ? ' v' + pkg.version : '') + ' initialized');
|
135
|
+
},
|
136
|
+
|
137
|
+
// initialize given phantomas module
|
138
|
+
addModule: function(name) {
|
139
|
+
var pkg;
|
140
|
+
try {
|
141
|
+
pkg = require('./../modules/' + name + '/' + name);
|
142
|
+
}
|
143
|
+
catch (e) {
|
144
|
+
this.log('Unable to load module "' + name + '"!');
|
145
|
+
return false;
|
146
|
+
}
|
147
|
+
|
148
|
+
if (pkg.skip) {
|
149
|
+
this.log('Module ' + name + ' skipped!');
|
150
|
+
return false;
|
151
|
+
}
|
152
|
+
|
153
|
+
// init a module
|
154
|
+
pkg.module(this.getPublicWrapper());
|
155
|
+
|
156
|
+
this.log('Module ' + name + (pkg.version ? ' v' + pkg.version : '') + ' initialized');
|
157
|
+
return true;
|
158
|
+
},
|
159
|
+
|
160
|
+
// returns list of 3rd party modules located in modules directory
|
161
|
+
listModules: function() {
|
162
|
+
this.log('Getting the list of all modules...');
|
163
|
+
|
164
|
+
var fs = require('fs'),
|
165
|
+
modulesDir = fs.workingDirectory + '/modules',
|
166
|
+
ls = fs.list(modulesDir) || [],
|
167
|
+
modules = [];
|
168
|
+
|
169
|
+
ls.forEach(function(entry) {
|
170
|
+
if (fs.isFile(modulesDir + '/' + entry + '/' + entry + '.js')) {
|
171
|
+
modules.push(entry);
|
172
|
+
}
|
173
|
+
});
|
174
|
+
|
175
|
+
return modules;
|
176
|
+
},
|
177
|
+
|
178
|
+
// runs phantomas
|
179
|
+
run: function(callback) {
|
180
|
+
|
181
|
+
// check required params
|
182
|
+
if (!this.url) {
|
183
|
+
throw '--url argument must be provided!';
|
184
|
+
}
|
185
|
+
|
186
|
+
// to be called by tearDown
|
187
|
+
this.onDoneCallback = callback;
|
188
|
+
|
189
|
+
this.start = Date.now();
|
190
|
+
|
191
|
+
// setup viewport
|
192
|
+
var parsedViewport = this.viewport.split('x');
|
193
|
+
|
194
|
+
if (parsedViewport.length === 2) {
|
195
|
+
this.page.viewportSize = {
|
196
|
+
height: parseInt(parsedViewport[0], 10) || 1280,
|
197
|
+
width: parseInt(parsedViewport[1], 10) || 1024
|
198
|
+
};
|
199
|
+
}
|
200
|
+
|
201
|
+
// setup user agent
|
202
|
+
if (this.userAgent) {
|
203
|
+
this.page.settings.userAgent = this.userAgent;
|
204
|
+
}
|
205
|
+
|
206
|
+
// print out debug messages
|
207
|
+
this.log('Opening <' + this.url + '>...');
|
208
|
+
this.log('Using ' + this.page.settings.userAgent + ' as user agent');
|
209
|
+
this.log('Viewport set to ' + this.page.viewportSize.height + 'x' + this.page.viewportSize.width);
|
210
|
+
|
211
|
+
// bind basic events
|
212
|
+
this.page.onInitialized = this.proxy(this.onInitialized);
|
213
|
+
this.page.onLoadStarted = this.proxy(this.onLoadStarted);
|
214
|
+
this.page.onLoadFinished = this.proxy(this.onLoadFinished);
|
215
|
+
this.page.onResourceRequested = this.proxy(this.onResourceRequested);
|
216
|
+
this.page.onResourceReceived = this.proxy(this.onResourceReceived);
|
217
|
+
|
218
|
+
// debug
|
219
|
+
this.page.onAlert = this.proxy(this.onAlert);
|
220
|
+
this.page.onConsoleMessage = this.proxy(this.onConsoleMessage);
|
221
|
+
|
222
|
+
// observe HTTP requests
|
223
|
+
// finish when the last request is completed
|
224
|
+
|
225
|
+
// update HTTP requests counter
|
226
|
+
this.on('send', this.proxy(function(entry) {
|
227
|
+
this.currentRequests++;
|
228
|
+
}));
|
229
|
+
|
230
|
+
this.on('recv', this.proxy(function(entry) {
|
231
|
+
this.currentRequests--;
|
232
|
+
|
233
|
+
this.enqueueReport();
|
234
|
+
}));
|
235
|
+
|
236
|
+
// last time changes?
|
237
|
+
this.emit('pageBeforeOpen', this.page);
|
238
|
+
|
239
|
+
// open the page
|
240
|
+
this.page.open(this.url);
|
241
|
+
|
242
|
+
this.emit('pageOpen');
|
243
|
+
|
244
|
+
// fallback - always timeout after TIMEOUT seconds
|
245
|
+
this.log('Run timeout set to ' + this.timeout + ' s');
|
246
|
+
setTimeout(this.proxy(function() {
|
247
|
+
this.log('Timeout of ' + this.timeout + ' s was reached!');
|
248
|
+
this.report();
|
249
|
+
}), this.timeout * 1000);
|
250
|
+
},
|
251
|
+
|
252
|
+
/**
|
253
|
+
* Wait a second before finishing the monitoring (i.e. report generation)
|
254
|
+
*
|
255
|
+
* This one is called when response is received. Previously scheduled reporting is removed and the new is created.
|
256
|
+
*/
|
257
|
+
enqueueReport: function() {
|
258
|
+
clearTimeout(this.lastRequestTimeout);
|
259
|
+
|
260
|
+
if (this.currentRequests < 1) {
|
261
|
+
this.lastRequestTimeout = setTimeout(this.proxy(this.report), 1000);
|
262
|
+
}
|
263
|
+
},
|
264
|
+
|
265
|
+
// called when all HTTP requests are completed
|
266
|
+
report: function() {
|
267
|
+
this.emit('report');
|
268
|
+
|
269
|
+
var time = Date.now() - this.start;
|
270
|
+
this.log('phantomas work done in ' + time + ' ms');
|
271
|
+
|
272
|
+
// format results
|
273
|
+
var results = {
|
274
|
+
url: this.url,
|
275
|
+
metrics: this.metrics,
|
276
|
+
notices: this.notices
|
277
|
+
};
|
278
|
+
|
279
|
+
this.emit('results', results);
|
280
|
+
|
281
|
+
// count all metrics
|
282
|
+
var metricsCount = 0,
|
283
|
+
i;
|
284
|
+
|
285
|
+
for (i in this.metrics) {
|
286
|
+
metricsCount++;
|
287
|
+
}
|
288
|
+
|
289
|
+
this.log('Formatting results (' + this.resultsFormat + ') with ' + metricsCount+ ' metric(s)...');
|
290
|
+
|
291
|
+
// render results
|
292
|
+
var formatter = require('./formatter').formatter,
|
293
|
+
renderer = new formatter(results, this.resultsFormat);
|
294
|
+
|
295
|
+
this.echo(renderer.render());
|
296
|
+
this.tearDown(0);
|
297
|
+
},
|
298
|
+
|
299
|
+
tearDown: function(exitCode) {
|
300
|
+
exitCode = exitCode || 0;
|
301
|
+
|
302
|
+
if (exitCode > 0) {
|
303
|
+
this.log('Exiting with code #' + exitCode);
|
304
|
+
}
|
305
|
+
|
306
|
+
this.page.release();
|
307
|
+
|
308
|
+
// call function provided to run() method
|
309
|
+
if (typeof this.onDoneCallback === 'function') {
|
310
|
+
this.onDoneCallback();
|
311
|
+
}
|
312
|
+
else {
|
313
|
+
phantom.exit(exitCode);
|
314
|
+
}
|
315
|
+
},
|
316
|
+
|
317
|
+
// core events
|
318
|
+
onInitialized: function() {
|
319
|
+
// add helper tools into window.phantomas "namespace"
|
320
|
+
this.page.injectJs('./core/helper.js');
|
321
|
+
|
322
|
+
this.log('Page object initialized');
|
323
|
+
this.emit('init');
|
324
|
+
},
|
325
|
+
|
326
|
+
onLoadStarted: function() {
|
327
|
+
this.log('Page loading started');
|
328
|
+
this.emit('loadStarted');
|
329
|
+
},
|
330
|
+
|
331
|
+
onResourceRequested: function(res) {
|
332
|
+
this.emit('onResourceRequested', res);
|
333
|
+
//this.log(JSON.stringify(res));
|
334
|
+
},
|
335
|
+
|
336
|
+
onResourceReceived: function(res) {
|
337
|
+
this.emit('onResourceReceived', res);
|
338
|
+
//this.log(JSON.stringify(res));
|
339
|
+
},
|
340
|
+
|
341
|
+
onLoadFinished: function(status) {
|
342
|
+
// trigger this only once
|
343
|
+
if (this.onLoadFinishedEmitted) {
|
344
|
+
return;
|
345
|
+
}
|
346
|
+
this.onLoadFinishedEmitted = true;
|
347
|
+
|
348
|
+
// we're done
|
349
|
+
this.log('Page loading finished ("' + status + '")');
|
350
|
+
|
351
|
+
switch(status) {
|
352
|
+
case 'success':
|
353
|
+
this.emit('loadFinished', status);
|
354
|
+
this.enqueueReport();
|
355
|
+
break;
|
356
|
+
|
357
|
+
default:
|
358
|
+
this.emit('loadFailed', status);
|
359
|
+
this.tearDown(2);
|
360
|
+
break;
|
361
|
+
}
|
362
|
+
},
|
363
|
+
|
364
|
+
// debug
|
365
|
+
onAlert: function(msg) {
|
366
|
+
this.log('Alert: ' + msg);
|
367
|
+
this.emit('alert', msg);
|
368
|
+
},
|
369
|
+
|
370
|
+
onConsoleMessage: function(msg) {
|
371
|
+
this.log('console.log: ' + msg);
|
372
|
+
this.emit('consoleLog', msg);
|
373
|
+
},
|
374
|
+
|
375
|
+
// metrics reporting
|
376
|
+
setMetric: function(name, value) {
|
377
|
+
this.metrics[name] = (typeof value !== 'undefined') ? value : 0;
|
378
|
+
},
|
379
|
+
|
380
|
+
setMetricEvaluate: function(name, fn) {
|
381
|
+
this.setMetric(name, this.page.evaluate(fn));
|
382
|
+
},
|
383
|
+
|
384
|
+
// increements given metric by given number (default is one)
|
385
|
+
incrMetric: function(name, incr /* =1 */) {
|
386
|
+
this.metrics[name] = (this.metrics[name] || 0) + (incr || 1);
|
387
|
+
},
|
388
|
+
|
389
|
+
// adds a notice that will be emitted after results
|
390
|
+
addNotice: function(msg) {
|
391
|
+
this.notices.push(msg || '');
|
392
|
+
},
|
393
|
+
|
394
|
+
// add log message
|
395
|
+
// will be printed out only when --verbose
|
396
|
+
log: function(msg) {
|
397
|
+
if (this.verboseMode) {
|
398
|
+
msg = (typeof msg === 'object') ? JSON.stringify(msg) : msg;
|
399
|
+
|
400
|
+
this.echo('> ' + msg);
|
401
|
+
}
|
402
|
+
},
|
403
|
+
|
404
|
+
// console.log wrapper obeying --silent mode
|
405
|
+
echo: function(msg) {
|
406
|
+
if (!this.silentMode) {
|
407
|
+
console.log(msg);
|
408
|
+
}
|
409
|
+
},
|
410
|
+
|
411
|
+
// require CommonJS module from lib/modules
|
412
|
+
require: function(module) {
|
413
|
+
return require('../lib/modules/' + module);
|
414
|
+
}
|
415
|
+
};
|
416
|
+
|
417
|
+
exports.phantomas = phantomas;
|
418
|
+
|