autobench 0.0.1alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +16 -0
  3. data/README.md +123 -0
  4. data/bin/autobench +180 -0
  5. data/bin/autobench-config +162 -0
  6. data/lib/autobench.rb +28 -0
  7. data/lib/autobench/client.rb +78 -0
  8. data/lib/autobench/common.rb +49 -0
  9. data/lib/autobench/config.rb +62 -0
  10. data/lib/autobench/render.rb +102 -0
  11. data/lib/autobench/version.rb +3 -0
  12. data/lib/autobench/yslow.rb +75 -0
  13. data/lib/phantomas/README.md +296 -0
  14. data/lib/phantomas/core/formatter.js +65 -0
  15. data/lib/phantomas/core/helper.js +64 -0
  16. data/lib/phantomas/core/modules/requestsMonitor/requestsMonitor.js +214 -0
  17. data/lib/phantomas/core/pads.js +16 -0
  18. data/lib/phantomas/core/phantomas.js +418 -0
  19. data/lib/phantomas/lib/args.js +27 -0
  20. data/lib/phantomas/lib/modules/_coffee-script.js +2 -0
  21. data/lib/phantomas/lib/modules/assert.js +326 -0
  22. data/lib/phantomas/lib/modules/events.js +216 -0
  23. data/lib/phantomas/lib/modules/http.js +55 -0
  24. data/lib/phantomas/lib/modules/path.js +441 -0
  25. data/lib/phantomas/lib/modules/punycode.js +510 -0
  26. data/lib/phantomas/lib/modules/querystring.js +214 -0
  27. data/lib/phantomas/lib/modules/tty.js +7 -0
  28. data/lib/phantomas/lib/modules/url.js +625 -0
  29. data/lib/phantomas/lib/modules/util.js +520 -0
  30. data/lib/phantomas/modules/ajaxRequests/ajaxRequests.js +15 -0
  31. data/lib/phantomas/modules/assetsTypes/assetsTypes.js +21 -0
  32. data/lib/phantomas/modules/cacheHits/cacheHits.js +28 -0
  33. data/lib/phantomas/modules/caching/caching.js +66 -0
  34. data/lib/phantomas/modules/cookies/cookies.js +54 -0
  35. data/lib/phantomas/modules/domComplexity/domComplexity.js +130 -0
  36. data/lib/phantomas/modules/domQueries/domQueries.js +148 -0
  37. data/lib/phantomas/modules/domains/domains.js +49 -0
  38. data/lib/phantomas/modules/globalVariables/globalVariables.js +44 -0
  39. data/lib/phantomas/modules/headers/headers.js +48 -0
  40. data/lib/phantomas/modules/localStorage/localStorage.js +14 -0
  41. data/lib/phantomas/modules/requestsStats/requestsStats.js +71 -0
  42. data/lib/phantomas/modules/staticAssets/staticAssets.js +40 -0
  43. data/lib/phantomas/modules/waterfall/waterfall.js +62 -0
  44. data/lib/phantomas/modules/windowPerformance/windowPerformance.js +36 -0
  45. data/lib/phantomas/package.json +27 -0
  46. data/lib/phantomas/phantomas.js +35 -0
  47. data/lib/phantomas/run-multiple.js +177 -0
  48. data/lib/yslow.js +5 -0
  49. 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
+