Commit 324be9d1 324be9d1e66d6b49a61ea6c244ba23edd7735749 by Nicolas Perriault

enhanced stack traces, introduced the CasperError standard error, better display…

… of test suite results
1 parent be8f9f4c
...@@ -5,6 +5,7 @@ XXXX-XX-XX, v0.6.0 ...@@ -5,6 +5,7 @@ XXXX-XX-XX, v0.6.0
5 ------------------ 5 ------------------
6 6
7 - **BC BREAK:** `Casper.click()` now uses native Webkit mouse events instead of previous crazy utopic javascript emulation 7 - **BC BREAK:** `Casper.click()` now uses native Webkit mouse events instead of previous crazy utopic javascript emulation
8 - **BC BREAK:** All errors thrown by CasperJS core are of the new `CasperError` type
8 - **BC BREAK:** removed obsolete `replaceFunctionPlaceholders()` 9 - **BC BREAK:** removed obsolete `replaceFunctionPlaceholders()`
9 - *Deprecated*: `Casper.extend()` method has been deprecated; use natural javascript extension mechanisms instead (see samples) 10 - *Deprecated*: `Casper.extend()` method has been deprecated; use natural javascript extension mechanisms instead (see samples)
10 - `Casper.open()` can now perform HTTP `GET`, `POST`, `PUT`, `DELETE` and `HEAD` operations 11 - `Casper.open()` can now perform HTTP `GET`, `POST`, `PUT`, `DELETE` and `HEAD` operations
...@@ -13,6 +14,7 @@ XXXX-XX-XX, v0.6.0 ...@@ -13,6 +14,7 @@ XXXX-XX-XX, v0.6.0
13 - introduced the `mouse` module to handle native Webkit mouse events 14 - introduced the `mouse` module to handle native Webkit mouse events
14 - added support for `RegExp` input in `Casper.resourceExists()` 15 - added support for `RegExp` input in `Casper.resourceExists()`
15 - added printing of source file path for any uncaught exception printed onto the console 16 - added printing of source file path for any uncaught exception printed onto the console
17 - added an emulation of stack trace printing (but PhantomJS will have to upgrade its javascript engine for it to be fully working though)
16 18
17 --- 19 ---
18 20
......
...@@ -89,6 +89,53 @@ if (!phantom.casperLoaded) { ...@@ -89,6 +89,53 @@ if (!phantom.casperLoaded) {
89 // Adding built-in capabilities to phantom object 89 // Adding built-in capabilities to phantom object
90 phantom.sourceIds = {}; 90 phantom.sourceIds = {};
91 91
92 // custom global CasperError
93 window.CasperError = function(msg) {
94 Error.call(this);
95 try {
96 // let's get where this error has been thrown from, if we can
97 this._from = arguments.callee.caller.name;
98 } catch (e) {
99 this._from = "anonymous";
100 }
101 this.message = msg;
102 this.name = 'CasperError';
103 };
104
105 // standard Error prototype inheritance
106 window.CasperError.prototype = Object.getPrototypeOf(new Error());
107
108 // Stack formatting
109 window.CasperError.prototype.formatStack = function() {
110 var location;
111 if (this.fileName || this.sourceId) {
112 location = (this.fileName || phantom.sourceIds[this.sourceId]);
113 } else {
114 location = "unknown";
115 }
116 location += this.line ? ':' + this.line : 0;
117 return this.toString() + '\n ' + (this._from || "anonymous") + '() at ' + location;
118 };
119
120 // Adding stack traces to CasperError
121 // Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/
122 // TODO: remove when phantomjs has js engine upgrade
123 if (!new CasperError().hasOwnProperty('stack')) {
124 Object.defineProperty(CasperError.prototype, 'stack', {
125 set: function(string) {
126 this._stack = string;
127 },
128 get: function() {
129 if (this._stack) {
130 return this._stack;
131 }
132 return this.formatStack();
133 },
134 configurable: true,
135 enumerable: true
136 });
137 }
138
92 phantom.getErrorMessage = function(e) { 139 phantom.getErrorMessage = function(e) {
93 return (e.fileName || this.sourceIds[e.sourceId]) + ':' + e.line + ' ' + e; 140 return (e.fileName || this.sourceIds[e.sourceId]) + ':' + e.line + ' ' + e;
94 }; 141 };
...@@ -190,30 +237,6 @@ if (!phantom.casperLoaded) { ...@@ -190,30 +237,6 @@ if (!phantom.casperLoaded) {
190 }; 237 };
191 })(require, phantom.casperPath); 238 })(require, phantom.casperPath);
192 239
193 // Adding stack traces to Error
194 // Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/
195 // TODO: remove when phantomjs has js engine upgrade
196 if (!new Error().hasOwnProperty('stack')) {
197 Object.defineProperty(Error.prototype, 'stack', {
198 set: function(string) {
199 this._stack = string;
200 },
201 get: function() {
202 var location;
203 if (this._stack) {
204 return this._stack;
205 } else if (this.fileName || this.sourceId) {
206 location = phantom.getErrorMessage(this);
207 } else {
208 location = "unknown";
209 }
210 return this.toString() + '\n at ' + location;
211 },
212 configurable: true,
213 enumerable: true
214 });
215 }
216
217 // BC < 0.6 240 // BC < 0.6
218 phantom.Casper = require('casper').Casper; 241 phantom.Casper = require('casper').Casper;
219 242
......
...@@ -166,7 +166,7 @@ Casper.prototype.capture = function(targetFile, clipRect) { ...@@ -166,7 +166,7 @@ Casper.prototype.capture = function(targetFile, clipRect) {
166 targetFile = fs.absolute(targetFile); 166 targetFile = fs.absolute(targetFile);
167 if (clipRect) { 167 if (clipRect) {
168 if (!utils.isClipRect(clipRect)) { 168 if (!utils.isClipRect(clipRect)) {
169 throw new Error("clipRect must be a valid ClipRect object."); 169 throw new CasperError("clipRect must be a valid ClipRect object.");
170 } 170 }
171 previousClipRect = this.page.clipRect; 171 previousClipRect = this.page.clipRect;
172 this.page.clipRect = clipRect; 172 this.page.clipRect = clipRect;
...@@ -260,7 +260,7 @@ Casper.prototype.click = function(selector, fallbackToHref) { ...@@ -260,7 +260,7 @@ Casper.prototype.click = function(selector, fallbackToHref) {
260 */ 260 */
261 Casper.prototype.createStep = function(fn, options) { 261 Casper.prototype.createStep = function(fn, options) {
262 if (!utils.isFunction(fn)) { 262 if (!utils.isFunction(fn)) {
263 throw new Error("createStep(): a step definition must be a function"); 263 throw new CasperError("createStep(): a step definition must be a function");
264 } 264 }
265 fn.options = utils.isObject(options) ? options : {}; 265 fn.options = utils.isObject(options) ? options : {};
266 this.emit('step.created', fn); 266 this.emit('step.created', fn);
...@@ -467,10 +467,10 @@ Casper.prototype.fetchText = function(selector) { ...@@ -467,10 +467,10 @@ Casper.prototype.fetchText = function(selector) {
467 Casper.prototype.fill = function(selector, vals, submit) { 467 Casper.prototype.fill = function(selector, vals, submit) {
468 submit = submit === true ? submit : false; 468 submit = submit === true ? submit : false;
469 if (!utils.isString(selector) || !selector.length) { 469 if (!utils.isString(selector) || !selector.length) {
470 throw new Error("Form selector must be a non-empty string"); 470 throw new CasperError("Form selector must be a non-empty string");
471 } 471 }
472 if (!utils.isObject(vals)) { 472 if (!utils.isObject(vals)) {
473 throw new Error("Form values must be provided as an object"); 473 throw new CasperError("Form values must be provided as an object");
474 } 474 }
475 this.emit('fill', selector, vals, submit); 475 this.emit('fill', selector, vals, submit);
476 var fillResults = this.evaluate(function(selector, values) { 476 var fillResults = this.evaluate(function(selector, values) {
...@@ -480,7 +480,7 @@ Casper.prototype.fill = function(selector, vals, submit) { ...@@ -480,7 +480,7 @@ Casper.prototype.fill = function(selector, vals, submit) {
480 values: vals 480 values: vals
481 }); 481 });
482 if (!fillResults) { 482 if (!fillResults) {
483 throw new Error("Unable to fill form"); 483 throw new CasperError("Unable to fill form");
484 } else if (fillResults.errors.length > 0) { 484 } else if (fillResults.errors.length > 0) {
485 (function(self){ 485 (function(self){
486 fillResults.errors.forEach(function(error) { 486 fillResults.errors.forEach(function(error) {
...@@ -546,13 +546,13 @@ Casper.prototype.getCurrentUrl = function() { ...@@ -546,13 +546,13 @@ Casper.prototype.getCurrentUrl = function() {
546 */ 546 */
547 Casper.prototype.getElementBounds = function(selector) { 547 Casper.prototype.getElementBounds = function(selector) {
548 if (!this.exists(selector)) { 548 if (!this.exists(selector)) {
549 throw new Error("No element matching selector found: " + selector); 549 throw new CasperError("No element matching selector found: " + selector);
550 } 550 }
551 var clipRect = this.evaluate(function(selector) { 551 var clipRect = this.evaluate(function(selector) {
552 return __utils__.getElementBounds(selector); 552 return __utils__.getElementBounds(selector);
553 }, { selector: selector }); 553 }, { selector: selector });
554 if (!utils.isClipRect(clipRect)) { 554 if (!utils.isClipRect(clipRect)) {
555 throw new Error('Could not fetch boundaries for element matching selector: ' + selector); 555 throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector);
556 } 556 }
557 return clipRect; 557 return clipRect;
558 }; 558 };
...@@ -576,7 +576,7 @@ Casper.prototype.getGlobal = function(name) { ...@@ -576,7 +576,7 @@ Casper.prototype.getGlobal = function(name) {
576 return result; 576 return result;
577 }, {'name': name}); 577 }, {'name': name});
578 if ('error' in result) { 578 if ('error' in result) {
579 throw new Error(result.error); 579 throw new CasperError(result.error);
580 } else if (utils.isString(result.value)) { 580 } else if (utils.isString(result.value)) {
581 return JSON.parse(result.value); 581 return JSON.parse(result.value);
582 } else { 582 } else {
...@@ -662,20 +662,20 @@ Casper.prototype.open = function(location, settings) { ...@@ -662,20 +662,20 @@ Casper.prototype.open = function(location, settings) {
662 }; 662 };
663 } 663 }
664 if (!utils.isObject(settings)) { 664 if (!utils.isObject(settings)) {
665 throw new Error("open(): request settings must be an Object"); 665 throw new CasperError("open(): request settings must be an Object");
666 } 666 }
667 // http method 667 // http method
668 // taken from https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L302 668 // taken from https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L302
669 var methods = ["get", "head", "put", "post", "delete"]; 669 var methods = ["get", "head", "put", "post", "delete"];
670 if (settings.method && (!utils.isString(settings.method) || methods.indexOf(settings.method) === -1)) { 670 if (settings.method && (!utils.isString(settings.method) || methods.indexOf(settings.method) === -1)) {
671 throw new Error("open(): settings.method must be part of " + methods.join(', ')); 671 throw new CasperError("open(): settings.method must be part of " + methods.join(', '));
672 } 672 }
673 // http data 673 // http data
674 if (settings.data) { 674 if (settings.data) {
675 if (utils.isObject(settings.data)) { // query object 675 if (utils.isObject(settings.data)) { // query object
676 settings.data = qs.encode(settings.data); 676 settings.data = qs.encode(settings.data);
677 } else if (!utils.isString(settings.data)) { 677 } else if (!utils.isString(settings.data)) {
678 throw new Error("open(): invalid request settings data value: " + settings.data); 678 throw new CasperError("open(): invalid request settings data value: " + settings.data);
679 } 679 }
680 } 680 }
681 // current request url 681 // current request url
...@@ -740,7 +740,7 @@ Casper.prototype.resourceExists = function(test) { ...@@ -740,7 +740,7 @@ Casper.prototype.resourceExists = function(test) {
740 testFn = test; 740 testFn = test;
741 break; 741 break;
742 default: 742 default:
743 throw new Error("Invalid type"); 743 throw new CasperError("Invalid type");
744 } 744 }
745 return this.resources.some(testFn); 745 return this.resources.some(testFn);
746 }; 746 };
...@@ -819,10 +819,10 @@ Casper.prototype.runStep = function(step) { ...@@ -819,10 +819,10 @@ Casper.prototype.runStep = function(step) {
819 */ 819 */
820 Casper.prototype.setHttpAuth = function(username, password) { 820 Casper.prototype.setHttpAuth = function(username, password) {
821 if (!this.started) { 821 if (!this.started) {
822 throw new Error("Casper must be started in order to use the setHttpAuth() method"); 822 throw new CasperError("Casper must be started in order to use the setHttpAuth() method");
823 } 823 }
824 if (!utils.isString(username) || !utils.isString(password)) { 824 if (!utils.isString(username) || !utils.isString(password)) {
825 throw new Error("Both username and password must be strings"); 825 throw new CasperError("Both username and password must be strings");
826 } 826 }
827 this.page.settings.userName = username; 827 this.page.settings.userName = username;
828 this.page.settings.password = password; 828 this.page.settings.password = password;
...@@ -899,10 +899,10 @@ Casper.prototype.start = function(location, then) { ...@@ -899,10 +899,10 @@ Casper.prototype.start = function(location, then) {
899 */ 899 */
900 Casper.prototype.then = function(step) { 900 Casper.prototype.then = function(step) {
901 if (!this.started) { 901 if (!this.started) {
902 throw new Error("Casper not started; please use Casper#start"); 902 throw new CasperError("Casper not started; please use Casper#start");
903 } 903 }
904 if (!utils.isFunction(step)) { 904 if (!utils.isFunction(step)) {
905 throw new Error("You can only define a step as a function"); 905 throw new CasperError("You can only define a step as a function");
906 } 906 }
907 // check if casper is running 907 // check if casper is running
908 if (this.checker === null) { 908 if (this.checker === null) {
...@@ -1002,10 +1002,10 @@ Casper.prototype.thenOpenAndEvaluate = function(location, fn, context) { ...@@ -1002,10 +1002,10 @@ Casper.prototype.thenOpenAndEvaluate = function(location, fn, context) {
1002 */ 1002 */
1003 Casper.prototype.viewport = function(width, height) { 1003 Casper.prototype.viewport = function(width, height) {
1004 if (!this.started) { 1004 if (!this.started) {
1005 throw new Error("Casper must be started in order to set viewport at runtime"); 1005 throw new CasperError("Casper must be started in order to set viewport at runtime");
1006 } 1006 }
1007 if (!utils.isNumber(width) || !utils.isNumber(height) || width <= 0 || height <= 0) { 1007 if (!utils.isNumber(width) || !utils.isNumber(height) || width <= 0 || height <= 0) {
1008 throw new Error(f("Invalid viewport: %dx%d", width, height)); 1008 throw new CasperError(f("Invalid viewport: %dx%d", width, height));
1009 } 1009 }
1010 this.page.viewportSize = { 1010 this.page.viewportSize = {
1011 width: width, 1011 width: width,
...@@ -1194,7 +1194,7 @@ Casper.prototype.waitWhileVisible = function(selector, then, onTimeout, timeout) ...@@ -1194,7 +1194,7 @@ Casper.prototype.waitWhileVisible = function(selector, then, onTimeout, timeout)
1194 Casper.extend = function(proto) { 1194 Casper.extend = function(proto) {
1195 console.warn('Casper.extend() has been deprecated since 0.6; check the docs'); 1195 console.warn('Casper.extend() has been deprecated since 0.6; check the docs');
1196 if (!utils.isObject(proto)) { 1196 if (!utils.isObject(proto)) {
1197 throw new Error("extends() only accept objects as prototypes"); 1197 throw new CasperError("extends() only accept objects as prototypes");
1198 } 1198 }
1199 utils.mergeObjects(Casper.prototype, proto); 1199 utils.mergeObjects(Casper.prototype, proto);
1200 }; 1200 };
...@@ -1254,7 +1254,7 @@ function createPage(casper) { ...@@ -1254,7 +1254,7 @@ function createPage(casper) {
1254 } 1254 }
1255 if (casper.options.clientScripts) { 1255 if (casper.options.clientScripts) {
1256 if (!utils.isArray(casper.options.clientScripts)) { 1256 if (!utils.isArray(casper.options.clientScripts)) {
1257 throw new Error("The clientScripts option must be an array"); 1257 throw new CasperError("The clientScripts option must be an array");
1258 } else { 1258 } else {
1259 casper.options.clientScripts.forEach(function(script) { 1259 casper.options.clientScripts.forEach(function(script) {
1260 if (casper.page.injectJs(script)) { 1260 if (casper.page.injectJs(script)) {
......
...@@ -47,7 +47,7 @@ exports.parse = function(phantomArgs) { ...@@ -47,7 +47,7 @@ exports.parse = function(phantomArgs) {
47 } else if (typeof what === "string") { 47 } else if (typeof what === "string") {
48 return this.options[what]; 48 return this.options[what];
49 } else { 49 } else {
50 throw new Error("Unsupported cli arg getter " + typeof what); 50 throw new CasperError("Unsupported cli arg getter " + typeof what);
51 } 51 }
52 } 52 }
53 }; 53 };
......
...@@ -47,7 +47,7 @@ EventEmitter.prototype.emit = function() { ...@@ -47,7 +47,7 @@ EventEmitter.prototype.emit = function() {
47 if (arguments[1] instanceof Error) { 47 if (arguments[1] instanceof Error) {
48 throw arguments[1]; // Unhandled 'error' event 48 throw arguments[1]; // Unhandled 'error' event
49 } else { 49 } else {
50 throw new Error("Uncaught, unspecified 'error' event."); 50 throw new CasperError("Uncaught, unspecified 'error' event.");
51 } 51 }
52 return false; 52 return false;
53 } 53 }
...@@ -98,7 +98,7 @@ EventEmitter.prototype.emit = function() { ...@@ -98,7 +98,7 @@ EventEmitter.prototype.emit = function() {
98 // EventEmitter.prototype.emit() is also defined there. 98 // EventEmitter.prototype.emit() is also defined there.
99 EventEmitter.prototype.addListener = function(type, listener) { 99 EventEmitter.prototype.addListener = function(type, listener) {
100 if ('function' !== typeof listener) { 100 if ('function' !== typeof listener) {
101 throw new Error('addListener only takes instances of Function'); 101 throw new CasperError('addListener only takes instances of Function');
102 } 102 }
103 103
104 if (!this._events) this._events = {}; 104 if (!this._events) this._events = {};
...@@ -145,7 +145,7 @@ EventEmitter.prototype.on = EventEmitter.prototype.addListener; ...@@ -145,7 +145,7 @@ EventEmitter.prototype.on = EventEmitter.prototype.addListener;
145 145
146 EventEmitter.prototype.once = function(type, listener) { 146 EventEmitter.prototype.once = function(type, listener) {
147 if ('function' !== typeof listener) { 147 if ('function' !== typeof listener) {
148 throw new Error('.once only takes instances of Function'); 148 throw new CasperError('.once only takes instances of Function');
149 } 149 }
150 150
151 var self = this; 151 var self = this;
...@@ -162,7 +162,7 @@ EventEmitter.prototype.once = function(type, listener) { ...@@ -162,7 +162,7 @@ EventEmitter.prototype.once = function(type, listener) {
162 162
163 EventEmitter.prototype.removeListener = function(type, listener) { 163 EventEmitter.prototype.removeListener = function(type, listener) {
164 if ('function' !== typeof listener) { 164 if ('function' !== typeof listener) {
165 throw new Error('removeListener only takes instances of Function'); 165 throw new CasperError('removeListener only takes instances of Function');
166 } 166 }
167 167
168 // does not use listeners(), so no side effect of creating _events[type] 168 // does not use listeners(), so no side effect of creating _events[type]
...@@ -231,7 +231,7 @@ EventEmitter.prototype.filter = function() { ...@@ -231,7 +231,7 @@ EventEmitter.prototype.filter = function() {
231 EventEmitter.prototype.setFilter = function(type, filterFn) { 231 EventEmitter.prototype.setFilter = function(type, filterFn) {
232 if (!this._filters) this._filters = {}; 232 if (!this._filters) this._filters = {};
233 if ('function' !== typeof filterFn) { 233 if ('function' !== typeof filterFn) {
234 throw new Error('setFilter only takes instances of Function'); 234 throw new CasperError('setFilter only takes instances of Function');
235 } 235 }
236 if (!this._filters[type]) { 236 if (!this._filters[type]) {
237 this._filters[type] = filterFn; 237 this._filters[type] = filterFn;
......
...@@ -41,7 +41,7 @@ exports.create = function(fn) { ...@@ -41,7 +41,7 @@ exports.create = function(fn) {
41 */ 41 */
42 var FunctionArgsInjector = function(fn) { 42 var FunctionArgsInjector = function(fn) {
43 if (!utils.isFunction(fn)) { 43 if (!utils.isFunction(fn)) {
44 throw new Error("FunctionArgsInjector() can only process functions"); 44 throw new CasperError("FunctionArgsInjector() can only process functions");
45 } 45 }
46 this.fn = fn; 46 this.fn = fn;
47 47
...@@ -64,7 +64,7 @@ var FunctionArgsInjector = function(fn) { ...@@ -64,7 +64,7 @@ var FunctionArgsInjector = function(fn) {
64 this.process = function(values) { 64 this.process = function(values) {
65 var fnObj = this.extract(this.fn); 65 var fnObj = this.extract(this.fn);
66 if (!utils.isObject(fnObj)) { 66 if (!utils.isObject(fnObj)) {
67 throw new Error("Unable to process function " + this.fn.toString()); 67 throw new CasperError("Unable to process function " + this.fn.toString());
68 } 68 }
69 var inject = this.getArgsInjectionString(fnObj.args, values); 69 var inject = this.getArgsInjectionString(fnObj.args, values);
70 return 'function ' + (fnObj.name || '') + '(){' + inject + fnObj.body + '}'; 70 return 'function ' + (fnObj.name || '') + '(){' + inject + fnObj.body + '}';
......
...@@ -36,62 +36,62 @@ exports.create = function(casper) { ...@@ -36,62 +36,62 @@ exports.create = function(casper) {
36 36
37 var Mouse = function(casper) { 37 var Mouse = function(casper) {
38 if (!utils.isCasperObject(casper)) { 38 if (!utils.isCasperObject(casper)) {
39 throw new Error('Mouse() needs a Casper instance'); 39 throw new CasperError('Mouse() needs a Casper instance');
40 } 40 }
41 41
42 var supportedEvents = ['mouseup', 'mousedown', 'click', 'mousemove']; 42 var supportedEvents = ['mouseup', 'mousedown', 'click', 'mousemove'];
43 43
44 var computeCenter = function(selector) { 44 function computeCenter(selector) {
45 var bounds = casper.getElementBounds(selector); 45 var bounds = casper.getElementBounds(selector);
46 if (utils.isClipRect(bounds)) { 46 if (utils.isClipRect(bounds)) {
47 var x = Math.round(bounds.left + bounds.width / 2); 47 var x = Math.round(bounds.left + bounds.width / 2);
48 var y = Math.round(bounds.top + bounds.height / 2); 48 var y = Math.round(bounds.top + bounds.height / 2);
49 return [x, y]; 49 return [x, y];
50 } 50 }
51 }; 51 }
52 52
53 var processEvent = function(type, args) { 53 function processEvent(type, args) {
54 if (!utils.isString(type) || supportedEvents.indexOf(type) === -1) { 54 if (!utils.isString(type) || supportedEvents.indexOf(type) === -1) {
55 throw new Error('Mouse.processEvent(): Unsupported mouse event type: ' + type); 55 throw new CasperError('Mouse.processEvent(): Unsupported mouse event type: ' + type);
56 } 56 }
57 args = Array.prototype.slice.call(args); // cast Arguments -> Array 57 args = Array.prototype.slice.call(args); // cast Arguments -> Array
58 casper.emit('mouse.' + type.replace('mouse', ''), args); 58 casper.emit('mouse.' + type.replace('mouse', ''), args);
59 switch (args.length) { 59 switch (args.length) {
60 case 0: 60 case 0:
61 throw new Error('Mouse.processEvent(): Too few arguments'); 61 throw new CasperError('Mouse.processEvent(): Too few arguments');
62 case 1: 62 case 1:
63 // selector 63 // selector
64 var selector = args[0]; 64 var selector = args[0];
65 if (!utils.isString(selector)) { 65 if (!utils.isString(selector)) {
66 throw new Error('Mouse.processEvent(): No valid CSS selector passed: ' + selector); 66 throw new CasperError('Mouse.processEvent(): No valid CSS selector passed: ' + selector);
67 } 67 }
68 casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(selector))); 68 casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(selector)));
69 break; 69 break;
70 case 2: 70 case 2:
71 // coordinates 71 // coordinates
72 if (!utils.isNumber(args[0]) || !utils.isNumber(args[1])) { 72 if (!utils.isNumber(args[0]) || !utils.isNumber(args[1])) {
73 throw new Error('Mouse.processEvent(): No valid coordinates passed: ' + args); 73 throw new CasperError('Mouse.processEvent(): No valid coordinates passed: ' + args);
74 } 74 }
75 casper.page.sendEvent(type, args[0], args[1]); 75 casper.page.sendEvent(type, args[0], args[1]);
76 break; 76 break;
77 default: 77 default:
78 throw new Error('Mouse.processEvent(): Too many arguments'); 78 throw new CasperError('Mouse.processEvent(): Too many arguments');
79 } 79 }
80 }; 80 }
81 81
82 this.click = function() { 82 this.click = function click() {
83 processEvent('click', arguments); 83 processEvent('click', arguments);
84 }; 84 };
85 85
86 this.down = function() { 86 this.down = function down() {
87 processEvent('mousedown', arguments); 87 processEvent('mousedown', arguments);
88 }; 88 };
89 89
90 this.move = function() { 90 this.move = function move() {
91 processEvent('mousemove', arguments); 91 processEvent('mousemove', arguments);
92 }; 92 };
93 93
94 this.up = function() { 94 this.up = function up() {
95 processEvent('mouseup', arguments); 95 processEvent('mouseup', arguments);
96 }; 96 };
97 }; 97 };
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
31 var fs = require('fs'); 31 var fs = require('fs');
32 var events = require('events'); 32 var events = require('events');
33 var utils = require('utils'); 33 var utils = require('utils');
34 var f = utils.format;
34 35
35 exports.create = function(casper, options) { 36 exports.create = function(casper, options) {
36 return new Tester(casper, options); 37 return new Tester(casper, options);
...@@ -47,7 +48,7 @@ var Tester = function(casper, options) { ...@@ -47,7 +48,7 @@ var Tester = function(casper, options) {
47 this.options = utils.isObject(options) ? options : {}; 48 this.options = utils.isObject(options) ? options : {};
48 49
49 if (!utils.isCasperObject(casper)) { 50 if (!utils.isCasperObject(casper)) {
50 throw new Error("Tester needs a Casper instance"); 51 throw new CasperError("Tester needs a Casper instance");
51 } 52 }
52 53
53 // locals 54 // locals
...@@ -309,7 +310,7 @@ var Tester = function(casper, options) { ...@@ -309,7 +310,7 @@ var Tester = function(casper, options) {
309 this.exec = function(file) { 310 this.exec = function(file) {
310 file = this.filter('exec.file', file) || file; 311 file = this.filter('exec.file', file) || file;
311 if (!fs.isFile(file) || !utils.isJsFile(file)) { 312 if (!fs.isFile(file) || !utils.isJsFile(file)) {
312 throw new Error("Can only exec() files with .js or .coffee extensions"); 313 throw new CasperError("Can only exec() files with .js or .coffee extensions");
313 } 314 }
314 this.currentTestFile = file; 315 this.currentTestFile = file;
315 try { 316 try {
...@@ -391,15 +392,20 @@ var Tester = function(casper, options) { ...@@ -391,15 +392,20 @@ var Tester = function(casper, options) {
391 this.assert(true, message); 392 this.assert(true, message);
392 }; 393 };
393 394
394 this.renderFailureDetails = function() { 395 /**
395 if (this.testResults.failures.length === 0) { 396 * Renders a detailed report for each failed test.
397 *
398 * @param Array failures
399 */
400 this.renderFailureDetails = function(failures) {
401 if (failures.length === 0) {
396 return; 402 return;
397 } 403 }
398 casper.echo("\nFailed test details\n"); 404 casper.echo(f("\nDetails for the %d failed tests:\n", failures.length), "PARAMETER");
399 this.testResults.failures.forEach(function(failure) { 405 failures.forEach(function(failure) {
400 casper.echo('In ' + failure.file + ':'); 406 casper.echo('In ' + failure.file + ':');
401 var message; 407 var message;
402 if (utils.isType(failure.message, "error")) { 408 if (utils.isType(failure.message, "object") && failure.message.stack) {
403 message = failure.message.stack; 409 message = failure.message.stack;
404 } else { 410 } else {
405 message = failure.message; 411 message = failure.message;
...@@ -432,7 +438,7 @@ var Tester = function(casper, options) { ...@@ -432,7 +438,7 @@ var Tester = function(casper, options) {
432 } 438 }
433 casper.echo(this.colorize(utils.fillBlanks(result), style)); 439 casper.echo(this.colorize(utils.fillBlanks(result), style));
434 if (this.testResults.failed > 0) { 440 if (this.testResults.failed > 0) {
435 this.renderFailureDetails(); 441 this.renderFailureDetails(this.testResults.failures);
436 } 442 }
437 if (save && utils.isFunction(require)) { 443 if (save && utils.isFunction(require)) {
438 try { 444 try {
...@@ -454,7 +460,7 @@ var Tester = function(casper, options) { ...@@ -454,7 +460,7 @@ var Tester = function(casper, options) {
454 this.runSuites = function() { 460 this.runSuites = function() {
455 var testFiles = [], self = this; 461 var testFiles = [], self = this;
456 if (arguments.length === 0) { 462 if (arguments.length === 0) {
457 throw new Error("No test suite to run"); 463 throw new CasperError("No test suite to run");
458 } 464 }
459 Array.prototype.forEach.call(arguments, function(path) { 465 Array.prototype.forEach.call(arguments, function(path) {
460 if (!fs.exists(path)) { 466 if (!fs.exists(path)) {
......
...@@ -268,7 +268,7 @@ exports.isString = isString; ...@@ -268,7 +268,7 @@ exports.isString = isString;
268 */ 268 */
269 function isType(what, typeName) { 269 function isType(what, typeName) {
270 if (typeof typeName !== "string" || !typeName) { 270 if (typeof typeName !== "string" || !typeName) {
271 throw new Error("You must pass isType() a typeName string"); 271 throw new CasperError("You must pass isType() a typeName string");
272 } 272 }
273 return betterTypeOf(what).toLowerCase() === typeName.toLowerCase(); 273 return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
274 } 274 }
......
...@@ -33,12 +33,12 @@ ...@@ -33,12 +33,12 @@
33 this.test.assertEquals(results.testdown, [200, 100], 'Mouse.down() has pressed button to the specified position'); 33 this.test.assertEquals(results.testdown, [200, 100], 'Mouse.down() has pressed button to the specified position');
34 34
35 t.comment('Mouse.up()'); 35 t.comment('Mouse.up()');
36 this.mouse.up(200, 100); 36 this.mouse.up(200, 200);
37 results = this.getGlobal('results'); 37 results = this.getGlobal('results');
38 this.test.assertEquals(results.testup, [200, 100], 'Mouse.up() has released button to the specified position'); 38 this.test.assertEquals(results.testup, [200, 100], 'Mouse.up() has released button to the specified position');
39 39
40 t.comment('Mouse.move()'); 40 t.comment('Mouse.move()');
41 this.mouse.move(200, 100); 41 this.mouse.move(200);
42 results = this.getGlobal('results'); 42 results = this.getGlobal('results');
43 this.test.assertEquals(results.testmove, [200, 100], 'Mouse.move() has moved to the specified position'); 43 this.test.assertEquals(results.testmove, [200, 100], 'Mouse.move() has moved to the specified position');
44 }); 44 });
......