autobench 0.0.1alpha1

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.
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
+