Commit 0d0e9621 0d0e962148663dd34fc54a8b27d1b481bc3d4c62 by Nicolas Perriault

added events where applicable in Casper methods

1 parent 582f1f9a
...@@ -11,7 +11,7 @@ def resolve(path): ...@@ -11,7 +11,7 @@ def resolve(path):
11 return path 11 return path
12 12
13 CASPER_PATH = os.path.abspath(os.path.join(os.path.dirname(resolve(__file__)), '..')) 13 CASPER_PATH = os.path.abspath(os.path.join(os.path.dirname(resolve(__file__)), '..'))
14 CASPER_ARGS = ['phantomjs', os.path.join(CASPER_PATH, 'bin', 'bootstrap.js'), '--casper-path=%s' % CASPER_PATH, '--cli'] 14 CASPER_ARGS = ['phantomjs14', os.path.join(CASPER_PATH, 'bin', 'bootstrap.js'), '--casper-path=%s' % CASPER_PATH, '--cli']
15 CASPER_ARGS.extend(sys.argv[1:]) 15 CASPER_ARGS.extend(sys.argv[1:])
16 16
17 try: 17 try:
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
26 * 26 *
27 */ 27 */
28 28
29 var events = require('events');
29 var fs = require('fs'); 30 var fs = require('fs');
30 var utils = require('utils'); 31 var utils = require('utils');
31 32
...@@ -104,24 +105,24 @@ var Casper = function(options) { ...@@ -104,24 +105,24 @@ var Casper = function(options) {
104 this.test = require('tester').create(this); 105 this.test = require('tester').create(this);
105 }; 106 };
106 107
108 utils.inherits(Casper, events.EventEmitter);
109
110
107 /** 111 /**
108 * Casper prototype
109 */
110 Casper.prototype = {
111 /**
112 * Go a step back in browser's history 112 * Go a step back in browser's history
113 * 113 *
114 * @return Casper 114 * @return Casper
115 */ 115 */
116 back: function() { 116 Casper.prototype.back = function() {
117 return this.then(function(self) { 117 return this.then(function() {
118 self.evaluate(function() { 118 this.emit('casper.back');
119 this.evaluate(function() {
119 history.back(); 120 history.back();
120 }); 121 });
121 }); 122 });
122 }, 123 };
123 124
124 /** 125 /**
125 * Encodes a resource using the base64 algorithm synchroneously using 126 * Encodes a resource using the base64 algorithm synchroneously using
126 * client-side XMLHttpRequest. 127 * client-side XMLHttpRequest.
127 * 128 *
...@@ -132,13 +133,13 @@ Casper.prototype = { ...@@ -132,13 +133,13 @@ Casper.prototype = {
132 * @param String data The data to send, optional 133 * @param String data The data to send, optional
133 * @return string Base64 encoded result 134 * @return string Base64 encoded result
134 */ 135 */
135 base64encode: function(url, method, data) { 136 Casper.prototype.base64encode = function(url, method, data) {
136 return this.evaluate(function(url, method, data) { 137 return this.evaluate(function(url, method, data) {
137 return __utils__.getBase64(url, method, data); 138 return __utils__.getBase64(url, method, data);
138 }, { url: url, method: method, data: data }); 139 }, { url: url, method: method, data: data });
139 }, 140 };
140 141
141 /** 142 /**
142 * Proxy method for WebPage#render. Adds a clipRect parameter for 143 * Proxy method for WebPage#render. Adds a clipRect parameter for
143 * automatically set page clipRect setting values and sets it back once 144 * automatically set page clipRect setting values and sets it back once
144 * done. If the cliprect parameter is omitted, the full page viewport 145 * done. If the cliprect parameter is omitted, the full page viewport
...@@ -148,7 +149,7 @@ Casper.prototype = { ...@@ -148,7 +149,7 @@ Casper.prototype = {
148 * @param mixed clipRect An optional clipRect object (optional) 149 * @param mixed clipRect An optional clipRect object (optional)
149 * @return Casper 150 * @return Casper
150 */ 151 */
151 capture: function(targetFile, clipRect) { 152 Casper.prototype.capture = function(targetFile, clipRect) {
152 var previousClipRect; 153 var previousClipRect;
153 targetFile = fs.absolute(targetFile); 154 targetFile = fs.absolute(targetFile);
154 if (clipRect) { 155 if (clipRect) {
...@@ -168,38 +169,38 @@ Casper.prototype = { ...@@ -168,38 +169,38 @@ Casper.prototype = {
168 this.page.clipRect = previousClipRect; 169 this.page.clipRect = previousClipRect;
169 } 170 }
170 return this; 171 return this;
171 }, 172 };
172 173
173 /** 174 /**
174 * Captures the page area containing the provided selector. 175 * Captures the page area containing the provided selector.
175 * 176 *
176 * @param String targetFile Target destination file path. 177 * @param String targetFile Target destination file path.
177 * @param String selector CSS3 selector 178 * @param String selector CSS3 selector
178 * @return Casper 179 * @return Casper
179 */ 180 */
180 captureSelector: function(targetFile, selector) { 181 Casper.prototype.captureSelector = function(targetFile, selector) {
181 return this.capture(targetFile, this.getElementBounds(selector)); 182 return this.capture(targetFile, this.getElementBounds(selector));
182 }, 183 };
183 184
184 /** 185 /**
185 * Checks for any further navigation step to process. 186 * Checks for any further navigation step to process.
186 * 187 *
187 * @param Casper self A self reference 188 * @param Casper self A self reference
188 * @param function onComplete An options callback to apply on completion 189 * @param function onComplete An options callback to apply on completion
189 */ 190 */
190 checkStep: function(self, onComplete) { 191 Casper.prototype.checkStep = function(self, onComplete) {
191 if (self.pendingWait || self.loadInProgress) { 192 if (self.pendingWait || self.loadInProgress) {
192 return; 193 return;
193 } 194 }
194 var step = self.steps[self.step++]; 195 var step = self.steps[self.step++];
195 if (utils.isType(step, "function")) { 196 if (utils.isFunction(step)) {
196 self.runStep(step); 197 self.runStep(step);
197 } else { 198 } else {
198 self.result.time = new Date().getTime() - self.startTime; 199 self.result.time = new Date().getTime() - self.startTime;
199 self.log("Done " + self.steps.length + " steps in " + self.result.time + 'ms.', "info"); 200 self.log("Done " + self.steps.length + " steps in " + self.result.time + 'ms.', "info");
200 self.page.content = ''; // avoid having previously loaded DOM contents being still active (refs #34) 201 self.page.content = ''; // avoid having previously loaded DOM contents being still active (refs #34)
201 clearInterval(self.checker); 202 clearInterval(self.checker);
202 if (utils.isType(onComplete, "function")) { 203 if (utils.isFunction(onComplete)) {
203 try { 204 try {
204 onComplete.call(self, self); 205 onComplete.call(self, self);
205 } catch (err) { 206 } catch (err) {
...@@ -210,9 +211,9 @@ Casper.prototype = { ...@@ -210,9 +211,9 @@ Casper.prototype = {
210 self.exit(); 211 self.exit();
211 } 212 }
212 } 213 }
213 }, 214 };
214 215
215 /** 216 /**
216 * Emulates a click on the element from the provided selector, if 217 * Emulates a click on the element from the provided selector, if
217 * possible. In case of success, `true` is returned. 218 * possible. In case of success, `true` is returned.
218 * 219 *
...@@ -220,82 +221,85 @@ Casper.prototype = { ...@@ -220,82 +221,85 @@ Casper.prototype = {
220 * @param Boolean fallbackToHref Whether to try to relocate to the value of any href attribute (default: true) 221 * @param Boolean fallbackToHref Whether to try to relocate to the value of any href attribute (default: true)
221 * @return Boolean 222 * @return Boolean
222 */ 223 */
223 click: function(selector, fallbackToHref) { 224 Casper.prototype.click = function(selector, fallbackToHref) {
224 fallbackToHref = utils.isType(fallbackToHref, "undefined") ? true : !!fallbackToHref; 225 fallbackToHref = utils.isType(fallbackToHref, "undefined") ? true : !!fallbackToHref;
225 this.log("Click on selector: " + selector, "debug"); 226 this.log("Click on selector: " + selector, "debug");
227 this.emit('casper.click', selector, fallbackToHref);
226 return this.evaluate(function(selector, fallbackToHref) { 228 return this.evaluate(function(selector, fallbackToHref) {
227 return __utils__.click(selector, fallbackToHref); 229 return __utils__.click(selector, fallbackToHref);
228 }, { 230 }, {
229 selector: selector, 231 selector: selector,
230 fallbackToHref: fallbackToHref 232 fallbackToHref: fallbackToHref
231 }); 233 });
232 }, 234 };
233 235
234 /** 236 /**
235 * Creates a step definition. 237 * Creates a step definition.
236 * 238 *
237 * @param Function fn The step function to call 239 * @param Function fn The step function to call
238 * @param Object options Step options 240 * @param Object options Step options
239 * @return Function The final step function 241 * @return Function The final step function
240 */ 242 */
241 createStep: function(fn, options) { 243 Casper.prototype.createStep = function(fn, options) {
242 if (!utils.isType(fn, "function")) { 244 if (!utils.isFunction(fn)) {
243 throw new Error("createStep(): a step definition must be a function"); 245 throw new Error("createStep(): a step definition must be a function");
244 } 246 }
245 fn.options = utils.isType(options, "object") ? options : {}; 247 fn.options = utils.isObject(options) ? options : {};
248 this.emit('casper.step.created', fn);
246 return fn; 249 return fn;
247 }, 250 };
248 251
249 /** 252 /**
250 * Logs the HTML code of the current page. 253 * Logs the HTML code of the current page.
251 * 254 *
252 * @return Casper 255 * @return Casper
253 */ 256 */
254 debugHTML: function() { 257 Casper.prototype.debugHTML = function() {
255 this.echo(this.evaluate(function() { 258 this.echo(this.evaluate(function() {
256 return document.body.innerHTML; 259 return document.body.innerHTML;
257 })); 260 }));
258 return this; 261 return this;
259 }, 262 };
260 263
261 /** 264 /**
262 * Logs the textual contents of the current page. 265 * Logs the textual contents of the current page.
263 * 266 *
264 * @return Casper 267 * @return Casper
265 */ 268 */
266 debugPage: function() { 269 Casper.prototype.debugPage = function() {
267 this.echo(this.evaluate(function() { 270 this.echo(this.evaluate(function() {
268 return document.body.innerText; 271 return document.body.innerText;
269 })); 272 }));
270 return this; 273 return this;
271 }, 274 };
272 275
273 /** 276 /**
274 * Exit phantom on failure, with a logged error message. 277 * Exit phantom on failure, with a logged error message.
275 * 278 *
276 * @param String message An optional error message 279 * @param String message An optional error message
277 * @param Number status An optional exit status code (must be > 0) 280 * @param Number status An optional exit status code (must be > 0)
278 * @return Casper 281 * @return Casper
279 */ 282 */
280 die: function(message, status) { 283 Casper.prototype.die = function(message, status) {
281 this.result.status = 'error'; 284 this.result.status = "error";
282 this.result.time = new Date().getTime() - this.startTime; 285 this.result.time = new Date().getTime() - this.startTime;
283 message = utils.isType(message, "string") && message.length > 0 ? message : DEFAULT_DIE_MESSAGE; 286 message = utils.isString(message) && message.length > 0 ? message : DEFAULT_DIE_MESSAGE;
284 this.log(message, "error"); 287 this.log(message, "error");
285 if (utils.isType(this.options.onDie, "function")) { 288 this.emit('casper.die', message, status);
289 if (utils.isFunction(this.options.onDie)) {
286 this.options.onDie.call(this, this, message, status); 290 this.options.onDie.call(this, this, message, status);
287 } 291 }
288 return this.exit(Number(status) > 0 ? Number(status) : 1); 292 return this.exit(~~status > 0 ? ~~status : 1);
289 }, 293 };
290 294
291 /** 295 /**
292 * Downloads a resource and saves it on the filesystem. 296 * Downloads a resource and saves it on the filesystem.
293 * 297 *
294 * @param String url The url of the resource to download 298 * @param String url The url of the resource to download
295 * @param String targetPath The destination file path 299 * @param String targetPath The destination file path
296 * @return Casper 300 * @return Casper
297 */ 301 */
298 download: function(url, targetPath) { 302 Casper.prototype.download = function(url, targetPath) {
299 var cu = require('clientutils').create(); 303 var cu = require('clientutils').create();
300 try { 304 try {
301 fs.write(targetPath, cu.decode(this.base64encode(url)), 'w'); 305 fs.write(targetPath, cu.decode(this.base64encode(url)), 'w');
...@@ -303,18 +307,18 @@ Casper.prototype = { ...@@ -303,18 +307,18 @@ Casper.prototype = {
303 this.log("Error while downloading " + url + " to " + targetPath + ": " + e, "error"); 307 this.log("Error while downloading " + url + " to " + targetPath + ": " + e, "error");
304 } 308 }
305 return this; 309 return this;
306 }, 310 };
307 311
308 /** 312 /**
309 * Iterates over the values of a provided array and execute a callback 313 * Iterates over the values of a provided array and execute a callback
310 * for each item. 314 * for @ item.
311 * 315 *
312 * @param Array array 316 * @param Array array
313 * @param Function fn Callback: function(self, item, index) 317 * @param Function fn Callback: function(self, item, index)
314 * @return Casper 318 * @return Casper
315 */ 319 */
316 each: function(array, fn) { 320 Casper.prototype.each = function(array, fn) {
317 if (!utils.isType(array, "array")) { 321 if (!utils.isArray(array)) {
318 this.log("each() only works with arrays", "error"); 322 this.log("each() only works with arrays", "error");
319 return this; 323 return this;
320 } 324 }
...@@ -324,20 +328,20 @@ Casper.prototype = { ...@@ -324,20 +328,20 @@ Casper.prototype = {
324 }); 328 });
325 })(this); 329 })(this);
326 return this; 330 return this;
327 }, 331 };
328 332
329 /** 333 /**
330 * Prints something to stdout. 334 * Prints something to stdout.
331 * 335 *
332 * @param String text A string to echo to stdout 336 * @param String text A string to echo to stdout
333 * @return Casper 337 * @return Casper
334 */ 338 */
335 echo: function(text, style) { 339 Casper.prototype.echo = function(text, style) {
336 console.log(style ? this.colorizer.colorize(text, style) : text); 340 console.log(style ? this.colorizer.colorize(text, style) : text);
337 return this; 341 return this;
338 }, 342 };
339 343
340 /** 344 /**
341 * Evaluates an expression in the page context, a bit like what 345 * Evaluates an expression in the page context, a bit like what
342 * WebPage#evaluate does, but the passed function can also accept 346 * WebPage#evaluate does, but the passed function can also accept
343 * parameters if a context Object is also passed: 347 * parameters if a context Object is also passed:
...@@ -360,13 +364,13 @@ Casper.prototype = { ...@@ -360,13 +364,13 @@ Casper.prototype = {
360 * @return mixed 364 * @return mixed
361 * @see WebPage#evaluate 365 * @see WebPage#evaluate
362 */ 366 */
363 evaluate: function(fn, context) { 367 Casper.prototype.evaluate = function(fn, context) {
364 context = utils.isType(context, "object") ? context : {}; 368 context = utils.isObject(context) ? context : {};
365 var newFn = require('injector').create(fn).process(context); 369 var newFn = require('injector').create(fn).process(context);
366 return this.page.evaluate(newFn); 370 return this.page.evaluate(newFn);
367 }, 371 };
368 372
369 /** 373 /**
370 * Evaluates an expression within the current page DOM and die() if it 374 * Evaluates an expression within the current page DOM and die() if it
371 * returns false. 375 * returns false.
372 * 376 *
...@@ -374,27 +378,27 @@ Casper.prototype = { ...@@ -374,27 +378,27 @@ Casper.prototype = {
374 * @param String message The error message to log 378 * @param String message The error message to log
375 * @return Casper 379 * @return Casper
376 */ 380 */
377 evaluateOrDie: function(fn, message) { 381 Casper.prototype.evaluateOrDie = function(fn, message) {
378 if (!this.evaluate(fn)) { 382 if (!this.evaluate(fn)) {
379 return this.die(message); 383 return this.die(message);
380 } 384 }
381 return this; 385 return this;
382 }, 386 };
383 387
384 /** 388 /**
385 * Checks if an element matching the provided CSS3 selector exists in 389 * Checks if an element matching the provided CSS3 selector exists in
386 * current page DOM. 390 * current page DOM.
387 * 391 *
388 * @param String selector A CSS3 selector 392 * @param String selector A CSS3 selector
389 * @return Boolean 393 * @return Boolean
390 */ 394 */
391 exists: function(selector) { 395 Casper.prototype.exists = function(selector) {
392 return this.evaluate(function(selector) { 396 return this.evaluate(function(selector) {
393 return __utils__.exists(selector); 397 return __utils__.exists(selector);
394 }, { selector: selector }); 398 }, { selector: selector });
395 }, 399 };
396 400
397 /** 401 /**
398 * Checks if an element matching the provided CSS3 selector is visible 402 * Checks if an element matching the provided CSS3 selector is visible
399 * current page DOM by checking that offsetWidth and offsetHeight are 403 * current page DOM by checking that offsetWidth and offsetHeight are
400 * both non-zero. 404 * both non-zero.
...@@ -402,51 +406,53 @@ Casper.prototype = { ...@@ -402,51 +406,53 @@ Casper.prototype = {
402 * @param String selector A CSS3 selector 406 * @param String selector A CSS3 selector
403 * @return Boolean 407 * @return Boolean
404 */ 408 */
405 visible: function(selector) { 409 Casper.prototype.visible = function(selector) {
406 return this.evaluate(function(selector) { 410 return this.evaluate(function(selector) {
407 return __utils__.visible(selector); 411 return __utils__.visible(selector);
408 }, { selector: selector }); 412 }, { selector: selector });
409 }, 413 };
410 414
411 /** 415 /**
412 * Exits phantom. 416 * Exits phantom.
413 * 417 *
414 * @param Number status Status 418 * @param Number status Status
415 * @return Casper 419 * @return Casper
416 */ 420 */
417 exit: function(status) { 421 Casper.prototype.exit = function(status) {
422 this.emit('casper.exit', status);
418 phantom.exit(status); 423 phantom.exit(status);
419 return this; 424 return this;
420 }, 425 };
421 426
422 /** 427 /**
423 * Fetches innerText within the element(s) matching a given CSS3 428 * Fetches innerText within the element(s) matching a given CSS3
424 * selector. 429 * selector.
425 * 430 *
426 * @param String selector A CSS3 selector 431 * @param String selector A CSS3 selector
427 * @return String 432 * @return String
428 */ 433 */
429 fetchText: function(selector) { 434 Casper.prototype.fetchText = function(selector) {
430 return this.evaluate(function(selector) { 435 return this.evaluate(function(selector) {
431 return __utils__.fetchText(selector); 436 return __utils__.fetchText(selector);
432 }, { selector: selector }); 437 }, { selector: selector });
433 }, 438 };
434 439
435 /** 440 /**
436 * Fills a form with provided field values. 441 * Fills a form with provided field values.
437 * 442 *
438 * @param String selector A CSS3 selector to the target form to fill 443 * @param String selector A CSS3 selector to the target form to fill
439 * @param Object vals Field values 444 * @param Object vals Field values
440 * @param Boolean submit Submit the form? 445 * @param Boolean submit Submit the form?
441 */ 446 */
442 fill: function(selector, vals, submit) { 447 Casper.prototype.fill = function(selector, vals, submit) {
443 submit = submit === true ? submit : false; 448 submit = submit === true ? submit : false;
444 if (!utils.isType(selector, "string") || !selector.length) { 449 if (!utils.isString(selector) || !selector.length) {
445 throw new Error("Form selector must be a non-empty string"); 450 throw new Error("Form selector must be a non-empty string");
446 } 451 }
447 if (!utils.isType(vals, "object")) { 452 if (!utils.isObject(vals)) {
448 throw new Error("Form values must be provided as an object"); 453 throw new Error("Form values must be provided as an object");
449 } 454 }
455 this.emit('casper.fill', selector, vals, submit);
450 var fillResults = this.evaluate(function(selector, values) { 456 var fillResults = this.evaluate(function(selector, values) {
451 return __utils__.fill(selector, values); 457 return __utils__.fill(selector, values);
452 }, { 458 }, {
...@@ -485,39 +491,40 @@ Casper.prototype = { ...@@ -485,39 +491,40 @@ Casper.prototype = {
485 form.submit(); 491 form.submit();
486 }, { selector: selector }); 492 }, { selector: selector });
487 } 493 }
488 }, 494 };
489 495
490 /** 496 /**
491 * Go a step forward in browser's history 497 * Go a step forward in browser's history
492 * 498 *
493 * @return Casper 499 * @return Casper
494 */ 500 */
495 forward: function(then) { 501 Casper.prototype.forward = function(then) {
496 return this.then(function(self) { 502 return this.then(function() {
497 self.evaluate(function() { 503 this.emit('casper.forward');
504 this.evaluate(function() {
498 history.forward(); 505 history.forward();
499 }); 506 });
500 }); 507 });
501 }, 508 };
502 509
503 /** 510 /**
504 * Retrieves current document url. 511 * Retrieves current document url.
505 * 512 *
506 * @return String 513 * @return String
507 */ 514 */
508 getCurrentUrl: function() { 515 Casper.prototype.getCurrentUrl = function() {
509 return decodeURIComponent(this.evaluate(function() { 516 return decodeURIComponent(this.evaluate(function() {
510 return document.location.href; 517 return document.location.href;
511 })); 518 }));
512 }, 519 };
513 520
514 /** 521 /**
515 * Retrieves boundaries for a DOM element matching the provided CSS3 selector. 522 * Retrieves boundaries for a DOM element matching the provided CSS3 selector.
516 * 523 *
517 * @param String selector A CSS3 selector 524 * @param String selector A CSS3 selector
518 * @return Object 525 * @return Object
519 */ 526 */
520 getElementBounds: function(selector) { 527 Casper.prototype.getElementBounds = function(selector) {
521 if (!this.exists(selector)) { 528 if (!this.exists(selector)) {
522 throw new Error("No element matching selector found: " + selector); 529 throw new Error("No element matching selector found: " + selector);
523 } 530 }
...@@ -528,15 +535,15 @@ Casper.prototype = { ...@@ -528,15 +535,15 @@ Casper.prototype = {
528 throw new Error('Could not fetch boundaries for element matching selector: ' + selector); 535 throw new Error('Could not fetch boundaries for element matching selector: ' + selector);
529 } 536 }
530 return clipRect; 537 return clipRect;
531 }, 538 };
532 539
533 /** 540 /**
534 * Retrieves global variable. 541 * Retrieves global variable.
535 * 542 *
536 * @param String name The name of the global variable to retrieve 543 * @param String name The name of the global variable to retrieve
537 * @return mixed 544 * @return mixed
538 */ 545 */
539 getGlobal: function(name) { 546 Casper.prototype.getGlobal = function(name) {
540 var result = this.evaluate(function(name) { 547 var result = this.evaluate(function(name) {
541 var result = {}; 548 var result = {};
542 try { 549 try {
...@@ -550,25 +557,25 @@ Casper.prototype = { ...@@ -550,25 +557,25 @@ Casper.prototype = {
550 }, {'name': name}); 557 }, {'name': name});
551 if ('error' in result) { 558 if ('error' in result) {
552 throw new Error(result.error); 559 throw new Error(result.error);
553 } else if (utils.isType(result.value, "string")) { 560 } else if (utils.isString(result.value)) {
554 return JSON.parse(result.value); 561 return JSON.parse(result.value);
555 } else { 562 } else {
556 return undefined; 563 return undefined;
557 } 564 }
558 }, 565 };
559 566
560 /** 567 /**
561 * Retrieves current page title, if any. 568 * Retrieves current page title, if any.
562 * 569 *
563 * @return String 570 * @return String
564 */ 571 */
565 getTitle: function() { 572 Casper.prototype.getTitle = function() {
566 return this.evaluate(function() { 573 return this.evaluate(function() {
567 return document.title; 574 return document.title;
568 }); 575 });
569 }, 576 };
570 577
571 /** 578 /**
572 * Logs a message. 579 * Logs a message.
573 * 580 *
574 * @param String message The message to log 581 * @param String message The message to log
...@@ -576,10 +583,10 @@ Casper.prototype = { ...@@ -576,10 +583,10 @@ Casper.prototype = {
576 * @param String space Space from where the logged event occured (default: "phantom") 583 * @param String space Space from where the logged event occured (default: "phantom")
577 * @return Casper 584 * @return Casper
578 */ 585 */
579 log: function(message, level, space) { 586 Casper.prototype.log = function(message, level, space) {
580 level = level && this.logLevels.indexOf(level) > -1 ? level : "debug"; 587 level = level && this.logLevels.indexOf(level) > -1 ? level : "debug";
581 space = space ? space : "phantom"; 588 space = space ? space : "phantom";
582 if (level === "error" && utils.isType(this.options.onError, "function")) { 589 if (level === "error" && utils.isFunction(this.options.onError)) {
583 this.options.onError.call(this, this, message, space); 590 this.options.onError.call(this, this, message, space);
584 } 591 }
585 if (this.logLevels.indexOf(level) < this.logLevels.indexOf(this.options.logLevel)) { 592 if (this.logLevels.indexOf(level) < this.logLevels.indexOf(this.options.logLevel)) {
...@@ -591,7 +598,7 @@ Casper.prototype = { ...@@ -591,7 +598,7 @@ Casper.prototype = {
591 message: message, 598 message: message,
592 date: new Date().toString() 599 date: new Date().toString()
593 }; 600 };
594 if (level in this.logFormats && utils.isType(this.logFormats[level], "function")) { 601 if (level in this.logFormats && utils.isFunction(this.logFormats[level])) {
595 message = this.logFormats[level](message, level, space); 602 message = this.logFormats[level](message, level, space);
596 } else { 603 } else {
597 var levelStr = this.colorizer.colorize('[' + level + ']', this.logStyles[level]); 604 var levelStr = this.colorizer.colorize('[' + level + ']', this.logStyles[level]);
...@@ -601,22 +608,23 @@ Casper.prototype = { ...@@ -601,22 +608,23 @@ Casper.prototype = {
601 this.echo(message); // direct output 608 this.echo(message); // direct output
602 } 609 }
603 this.result.log.push(entry); 610 this.result.log.push(entry);
611 this.emit('casper.log', entry);
604 return this; 612 return this;
605 }, 613 };
606 614
607 /** 615 /**
608 * Emulates a click on an HTML element matching a given CSS3 selector, 616 * Emulates a click on an HTML element matching a given CSS3 selector,
609 * using the mouse pointer. 617 * using the mouse pointer.
610 * 618 *
611 * @param String selector A DOM CSS3 compatible selector 619 * @param String selector A DOM CSS3 compatible selector
612 * @return Casper 620 * @return Casper
613 */ 621 */
614 mouseClick: function(selector) { 622 Casper.prototype.mouseClick = function(selector) {
615 this.mouse.click(selector); 623 this.mouse.click(selector);
616 return this; 624 return this;
617 }, 625 };
618 626
619 /** 627 /**
620 * Opens a page. Takes only one argument, the url to open (using the 628 * Opens a page. Takes only one argument, the url to open (using the
621 * callback argument would defeat the whole purpose of Casper 629 * callback argument would defeat the whole purpose of Casper
622 * actually). 630 * actually).
...@@ -624,19 +632,25 @@ Casper.prototype = { ...@@ -624,19 +632,25 @@ Casper.prototype = {
624 * @param String location The url to open 632 * @param String location The url to open
625 * @return Casper 633 * @return Casper
626 */ 634 */
627 open: function(location, options) { 635 Casper.prototype.open = function(location, options) {
628 options = utils.isType(options, "object") ? options : {}; 636 options = utils.isObject(options) ? options : {};
629 this.requestUrl = location; 637 this.requestUrl = location;
630 // http auth 638 // http auth
631 var httpAuthMatch = location.match(/^https?:\/\/(.+):(.+)@/i); 639 var httpAuthMatch = location.match(/^https?:\/\/(.+):(.+)@/i);
632 if (httpAuthMatch) { 640 if (httpAuthMatch) {
633 this.setHttpAuth(httpAuthMatch[1], httpAuthMatch[2]); 641 var httpAuth = {
642 username: httpAuthMatch[1],
643 password: httpAuthMatch[2]
644 };
645 this.emit('casper.http.auth', httpAuth);
646 this.setHttpAuth(httpAuth.username, httpAuth.password);
634 } 647 }
648 this.emit('casper.open', location);
635 this.page.open(location); 649 this.page.open(location);
636 return this; 650 return this;
637 }, 651 };
638 652
639 /** 653 /**
640 * Repeats a step a given number of times. 654 * Repeats a step a given number of times.
641 * 655 *
642 * @param Number times Number of times to repeat step 656 * @param Number times Number of times to repeat step
...@@ -644,22 +658,22 @@ Casper.prototype = { ...@@ -644,22 +658,22 @@ Casper.prototype = {
644 * @return Casper 658 * @return Casper
645 * @see Casper#then 659 * @see Casper#then
646 */ 660 */
647 repeat: function(times, then) { 661 Casper.prototype.repeat = function(times, then) {
648 for (var i = 0; i < times; i++) { 662 for (var i = 0; i < times; i++) {
649 this.then(then); 663 this.then(then);
650 } 664 }
651 return this; 665 return this;
652 }, 666 };
653 667
654 /** 668 /**
655 * Checks if a given resource was loaded by the remote page. 669 * Checks if a given resource was loaded by the remote page.
656 * 670 *
657 * @param Function/String test A test function or string. In case a string is passed, url matching will be tested. 671 * @param Function/String test A test function or string. In case a string is passed, url matching will be tested.
658 * @return Boolean 672 * @return Boolean
659 */ 673 */
660 resourceExists: function(test) { 674 Casper.prototype.resourceExists = function(test) {
661 var testFn; 675 var testFn;
662 if (utils.isType(test, "string")) { 676 if (utils.isString(test)) {
663 testFn = function (res) { 677 testFn = function (res) {
664 return res.url.search(test) !== -1; 678 return res.url.search(test) !== -1;
665 }; 679 };
...@@ -667,42 +681,44 @@ Casper.prototype = { ...@@ -667,42 +681,44 @@ Casper.prototype = {
667 testFn = test; 681 testFn = test;
668 } 682 }
669 return this.resources.some(testFn); 683 return this.resources.some(testFn);
670 }, 684 };
671 685
672 /** 686 /**
673 * Runs the whole suite of steps. 687 * Runs the whole suite of steps.
674 * 688 *
675 * @param function onComplete an optional callback 689 * @param function onComplete an optional callback
676 * @param Number time an optional amount of milliseconds for interval checking 690 * @param Number time an optional amount of milliseconds for interval checking
677 * @return Casper 691 * @return Casper
678 */ 692 */
679 run: function(onComplete, time) { 693 Casper.prototype.run = function(onComplete, time) {
680 if (!this.steps || this.steps.length < 1) { 694 if (!this.steps || this.steps.length < 1) {
681 this.log("No steps defined, aborting", "error"); 695 this.log("No steps defined, aborting", "error");
682 return this; 696 return this;
683 } 697 }
684 this.log("Running suite: " + this.steps.length + " step" + (this.steps.length > 1 ? "s" : ""), "info"); 698 this.log("Running suite: " + this.steps.length + " step" + (this.steps.length > 1 ? "s" : ""), "info");
699 this.emit('casper.run');
685 this.checker = setInterval(this.checkStep, (time ? time: 250), this, onComplete); 700 this.checker = setInterval(this.checkStep, (time ? time: 250), this, onComplete);
686 return this; 701 return this;
687 }, 702 };
688 703
689 /** 704 /**
690 * Runs a step. 705 * Runs a step.
691 * 706 *
692 * @param Function step 707 * @param Function step
693 */ 708 */
694 runStep: function(step) { 709 Casper.prototype.runStep = function(step) {
695 var skipLog = utils.isType(step.options, "object") && step.options.skipLog === true; 710 var skipLog = utils.isObject(step.options) && step.options.skipLog === true;
696 var stepInfo = "Step " + (this.step) + "/" + this.steps.length; 711 var stepInfo = "Step " + (this.step) + "/" + this.steps.length;
697 var stepResult; 712 var stepResult;
698 if (!skipLog) { 713 if (!skipLog) {
699 this.log(stepInfo + ' ' + this.getCurrentUrl() + ' (HTTP ' + this.currentHTTPStatus + ')', "info"); 714 this.log(stepInfo + ' ' + this.getCurrentUrl() + ' (HTTP ' + this.currentHTTPStatus + ')', "info");
700 } 715 }
701 if (utils.isType(this.options.stepTimeout, "number") && this.options.stepTimeout > 0) { 716 if (utils.isNumber(this.options.stepTimeout) && this.options.stepTimeout > 0) {
702 var stepTimeoutCheckInterval = setInterval(function(self, start, stepNum) { 717 var stepTimeoutCheckInterval = setInterval(function(self, start, stepNum) {
703 if (new Date().getTime() - start > self.options.stepTimeout) { 718 if (new Date().getTime() - start > self.options.stepTimeout) {
704 if (self.step == stepNum) { 719 if (self.step == stepNum) {
705 if (utils.isType(self.options.onStepTimeout, "function")) { 720 self.emit('casper.step.timeout');
721 if (utils.isFunction(self.options.onStepTimeout)) {
706 self.options.onStepTimeout.call(self, self); 722 self.options.onStepTimeout.call(self, self);
707 } else { 723 } else {
708 self.die("Maximum step execution timeout exceeded for step " + stepNum, "error"); 724 self.die("Maximum step execution timeout exceeded for step " + stepNum, "error");
...@@ -712,6 +728,7 @@ Casper.prototype = { ...@@ -712,6 +728,7 @@ Casper.prototype = {
712 } 728 }
713 }, this.options.stepTimeout, this, new Date().getTime(), this.step); 729 }, this.options.stepTimeout, this, new Date().getTime(), this.step);
714 } 730 }
731 this.emit('casper.step.start', step);
715 try { 732 try {
716 stepResult = step.call(this, this); 733 stepResult = step.call(this, this);
717 } catch (e) { 734 } catch (e) {
...@@ -721,42 +738,44 @@ Casper.prototype = { ...@@ -721,42 +738,44 @@ Casper.prototype = {
721 throw e; 738 throw e;
722 } 739 }
723 } 740 }
724 if (utils.isType(this.options.onStepComplete, "function")) { 741 if (utils.isFunction(this.options.onStepComplete)) {
725 this.options.onStepComplete.call(this, this, stepResult); 742 this.options.onStepComplete.call(this, this, stepResult);
726 } 743 }
727 if (!skipLog) { 744 if (!skipLog) {
745 this.emit('casper.step.complete', stepResult);
728 this.log(stepInfo + ": done in " + (new Date().getTime() - this.startTime) + "ms.", "info"); 746 this.log(stepInfo + ": done in " + (new Date().getTime() - this.startTime) + "ms.", "info");
729 } 747 }
730 }, 748 };
731 749
732 /** 750 /**
733 * Sets HTTP authentication parameters. 751 * Sets HTTP authentication parameters.
734 * 752 *
735 * @param String username The HTTP_AUTH_USER value 753 * @param String username The HTTP_AUTH_USER value
736 * @param String password The HTTP_AUTH_PW value 754 * @param String password The HTTP_AUTH_PW value
737 * @return Casper 755 * @return Casper
738 */ 756 */
739 setHttpAuth: function(username, password) { 757 Casper.prototype.setHttpAuth = function(username, password) {
740 if (!this.started) { 758 if (!this.started) {
741 throw new Error("Casper must be started in order to use the setHttpAuth() method"); 759 throw new Error("Casper must be started in order to use the setHttpAuth() method");
742 } 760 }
743 if (!utils.isType(username, "string") || !utils.isType(password, "string")) { 761 if (!utils.isString(username) || !utils.isString(password)) {
744 throw new Error("Both username and password must be strings"); 762 throw new Error("Both username and password must be strings");
745 } 763 }
746 this.page.settings.userName = username; 764 this.page.settings.userName = username;
747 this.page.settings.password = password; 765 this.page.settings.password = password;
748 this.log("Setting HTTP authentication for user " + username, "info"); 766 this.log("Setting HTTP authentication for user " + username, "info");
749 return this; 767 return this;
750 }, 768 };
751 769
752 /** 770 /**
753 * Configures and starts Casper. 771 * Configures and starts Casper.
754 * 772 *
755 * @param String location An optional location to open on start 773 * @param String location An optional location to open on start
756 * @param function then Next step function to execute on page loaded (optional) 774 * @param function then Next step function to execute on page loaded (optional)
757 * @return Casper 775 * @return Casper
758 */ 776 */
759 start: function(location, then) { 777 Casper.prototype.start = function(location, then) {
778 this.emit('casper.starting');
760 this.log('Starting...', "info"); 779 this.log('Starting...', "info");
761 this.startTime = new Date().getTime(); 780 this.startTime = new Date().getTime();
762 this.history = []; 781 this.history = [];
...@@ -776,46 +795,49 @@ Casper.prototype = { ...@@ -776,46 +795,49 @@ Casper.prototype = {
776 } 795 }
777 } 796 }
778 this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings); 797 this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings);
779 if (utils.isType(this.options.clipRect, "object")) { 798 if (utils.isClipRect(this.options.clipRect)) {
780 this.page.clipRect = this.options.clipRect; 799 this.page.clipRect = this.options.clipRect;
781 } 800 }
782 if (utils.isType(this.options.viewportSize, "object")) { 801 if (utils.isObject(this.options.viewportSize)) {
783 this.page.viewportSize = this.options.viewportSize; 802 this.page.viewportSize = this.options.viewportSize;
784 } 803 }
785 this.started = true; 804 this.started = true;
786 if (utils.isType(this.options.timeout, "number") && this.options.timeout > 0) { 805 this.emit('casper.started');
806 if (utils.isNumber(this.options.timeout) && this.options.timeout > 0) {
787 this.log("Execution timeout set to " + this.options.timeout + 'ms', "info"); 807 this.log("Execution timeout set to " + this.options.timeout + 'ms', "info");
788 setTimeout(function(self) { 808 setTimeout(function(self) {
789 if (utils.isType(self.options.onTimeout, "function")) { 809 self.emit('casper.timeout');
810 if (utils.isFunction(self.options.onTimeout)) {
790 self.options.onTimeout.call(self, self); 811 self.options.onTimeout.call(self, self);
791 } else { 812 } else {
792 self.die("Timeout of " + self.options.timeout + "ms exceeded, exiting."); 813 self.die("Timeout of " + self.options.timeout + "ms exceeded, exiting.");
793 } 814 }
794 }, this.options.timeout, this); 815 }, this.options.timeout, this);
795 } 816 }
796 if (utils.isType(this.options.onPageInitialized, "function")) { 817 casper.emit('casper.page.initialized');
818 if (utils.isFunction(this.options.onPageInitialized)) {
797 this.log("Post-configuring WebPage instance", "debug"); 819 this.log("Post-configuring WebPage instance", "debug");
798 this.options.onPageInitialized.call(this, this.page); 820 this.options.onPageInitialized.call(this, this.page);
799 } 821 }
800 if (utils.isType(location, "string") && location.length > 0) { 822 if (utils.isString(location) && location.length > 0) {
801 return this.thenOpen(location, utils.isType(then, "function") ? then : this.createStep(function(self) { 823 return this.thenOpen(location, utils.isFunction(then) ? then : this.createStep(function(self) {
802 self.log("start page is loaded", "debug"); 824 self.log("start page is loaded", "debug");
803 })); 825 }));
804 } 826 }
805 return this; 827 return this;
806 }, 828 };
807 829
808 /** 830 /**
809 * Schedules the next step in the navigation process. 831 * Schedules the next step in the navigation process.
810 * 832 *
811 * @param function step A function to be called as a step 833 * @param function step A function to be called as a step
812 * @return Casper 834 * @return Casper
813 */ 835 */
814 then: function(step) { 836 Casper.prototype.then = function(step) {
815 if (!this.started) { 837 if (!this.started) {
816 throw new Error("Casper not started; please use Casper#start"); 838 throw new Error("Casper not started; please use Casper#start");
817 } 839 }
818 if (!utils.isType(step, "function")) { 840 if (!utils.isFunction(step)) {
819 throw new Error("You can only define a step as a function"); 841 throw new Error("You can only define a step as a function");
820 } 842 }
821 // check if casper is running 843 // check if casper is running
...@@ -836,10 +858,11 @@ Casper.prototype = { ...@@ -836,10 +858,11 @@ Casper.prototype = {
836 } 858 }
837 this.steps.splice(insertIndex, 0, step); 859 this.steps.splice(insertIndex, 0, step);
838 } 860 }
861 this.emit('casper.step.added', step);
839 return this; 862 return this;
840 }, 863 };
841 864
842 /** 865 /**
843 * Adds a new navigation step for clicking on a provided link selector 866 * Adds a new navigation step for clicking on a provided link selector
844 * and execute an optional next step. 867 * and execute an optional next step.
845 * 868 *
...@@ -850,14 +873,14 @@ Casper.prototype = { ...@@ -850,14 +873,14 @@ Casper.prototype = {
850 * @see Casper#click 873 * @see Casper#click
851 * @see Casper#then 874 * @see Casper#then
852 */ 875 */
853 thenClick: function(selector, then, fallbackToHref) { 876 Casper.prototype.thenClick = function(selector, then, fallbackToHref) {
854 this.then(function(self) { 877 this.then(function(self) {
855 self.click(selector, fallbackToHref); 878 self.click(selector, fallbackToHref);
856 }); 879 });
857 return utils.isType(then, "function") ? this.then(then) : this; 880 return utils.isFunction(then) ? this.then(then) : this;
858 }, 881 };
859 882
860 /** 883 /**
861 * Adds a new navigation step to perform code evaluation within the 884 * Adds a new navigation step to perform code evaluation within the
862 * current retrieved page DOM. 885 * current retrieved page DOM.
863 * 886 *
...@@ -866,13 +889,13 @@ Casper.prototype = { ...@@ -866,13 +889,13 @@ Casper.prototype = {
866 * @return Casper 889 * @return Casper
867 * @see Casper#evaluate 890 * @see Casper#evaluate
868 */ 891 */
869 thenEvaluate: function(fn, context) { 892 Casper.prototype.thenEvaluate = function(fn, context) {
870 return this.then(function(self) { 893 return this.then(function(self) {
871 self.evaluate(fn, context); 894 self.evaluate(fn, context);
872 }); 895 });
873 }, 896 };
874 897
875 /** 898 /**
876 * Adds a new navigation step for opening the provided location. 899 * Adds a new navigation step for opening the provided location.
877 * 900 *
878 * @param String location The URL to load 901 * @param String location The URL to load
...@@ -880,16 +903,16 @@ Casper.prototype = { ...@@ -880,16 +903,16 @@ Casper.prototype = {
880 * @return Casper 903 * @return Casper
881 * @see Casper#open 904 * @see Casper#open
882 */ 905 */
883 thenOpen: function(location, then) { 906 Casper.prototype.thenOpen = function(location, then) {
884 this.then(this.createStep(function(self) { 907 this.then(this.createStep(function(self) {
885 self.open(location); 908 self.open(location);
886 }, { 909 }, {
887 skipLog: true 910 skipLog: true
888 })); 911 }));
889 return utils.isType(then, "function") ? this.then(then) : this; 912 return utils.isFunction(then) ? this.then(then) : this;
890 }, 913 };
891 914
892 /** 915 /**
893 * Adds a new navigation step for opening and evaluate an expression 916 * Adds a new navigation step for opening and evaluate an expression
894 * against the DOM retrieved from the provided location. 917 * against the DOM retrieved from the provided location.
895 * 918 *
...@@ -900,32 +923,33 @@ Casper.prototype = { ...@@ -900,32 +923,33 @@ Casper.prototype = {
900 * @see Casper#evaluate 923 * @see Casper#evaluate
901 * @see Casper#open 924 * @see Casper#open
902 */ 925 */
903 thenOpenAndEvaluate: function(location, fn, context) { 926 Casper.prototype.thenOpenAndEvaluate = function(location, fn, context) {
904 return this.thenOpen(location).thenEvaluate(fn, context); 927 return this.thenOpen(location).thenEvaluate(fn, context);
905 }, 928 };
906 929
907 /** 930 /**
908 * Changes the current viewport size. 931 * Changes the current viewport size.
909 * 932 *
910 * @param Number width The viewport width, in pixels 933 * @param Number width The viewport width, in pixels
911 * @param Number height The viewport height, in pixels 934 * @param Number height The viewport height, in pixels
912 * @return Casper 935 * @return Casper
913 */ 936 */
914 viewport: function(width, height) { 937 Casper.prototype.viewport = function(width, height) {
915 if (!this.started) { 938 if (!this.started) {
916 throw new Error("Casper must be started in order to set viewport at runtime"); 939 throw new Error("Casper must be started in order to set viewport at runtime");
917 } 940 }
918 if (!utils.isType(width, "number") || !utils.isType(height, "number") || width <= 0 || height <= 0) { 941 if (!utils.isNumber(width) || !utils.isNumber(height) || width <= 0 || height <= 0) {
919 throw new Error("Invalid viewport width/height set: " + width + 'x' + height); 942 throw new Error("Invalid viewport width/height set: " + width + 'x' + height);
920 } 943 }
921 this.page.viewportSize = { 944 this.page.viewportSize = {
922 width: width, 945 width: width,
923 height: height 946 height: height
924 }; 947 };
948 this.emit('casper.viewport.changed', [width, height]);
925 return this; 949 return this;
926 }, 950 };
927 951
928 /** 952 /**
929 * Adds a new step that will wait for a given amount of time (expressed 953 * Adds a new step that will wait for a given amount of time (expressed
930 * in milliseconds) before processing an optional next one. 954 * in milliseconds) before processing an optional next one.
931 * 955 *
...@@ -933,12 +957,12 @@ Casper.prototype = { ...@@ -933,12 +957,12 @@ Casper.prototype = {
933 * @param Function then Next step to process (optional) 957 * @param Function then Next step to process (optional)
934 * @return Casper 958 * @return Casper
935 */ 959 */
936 wait: function(timeout, then) { 960 Casper.prototype.wait = function(timeout, then) {
937 timeout = Number(timeout, 10); 961 timeout = Number(timeout, 10);
938 if (!utils.isType(timeout, "number") || timeout < 1) { 962 if (!utils.isNumber(timeout) || timeout < 1) {
939 this.die("wait() only accepts a positive integer > 0 as a timeout value"); 963 this.die("wait() only accepts a positive integer > 0 as a timeout value");
940 } 964 }
941 if (then && !utils.isType(then, "function")) { 965 if (then && !utils.isFunction(then)) {
942 this.die("wait() a step definition must be a function"); 966 this.die("wait() a step definition must be a function");
943 } 967 }
944 return this.then(function(self) { 968 return this.then(function(self) {
...@@ -951,17 +975,19 @@ Casper.prototype = { ...@@ -951,17 +975,19 @@ Casper.prototype = {
951 self.waitDone(); 975 self.waitDone();
952 }, timeout); 976 }, timeout);
953 }); 977 });
954 }, 978 };
955 979
956 waitStart: function() { 980 Casper.prototype.waitStart = function() {
981 this.emit('casper.wait.start');
957 this.pendingWait = true; 982 this.pendingWait = true;
958 }, 983 };
959 984
960 waitDone: function() { 985 Casper.prototype.waitDone = function() {
986 this.emit('casper.wait.done');
961 this.pendingWait = false; 987 this.pendingWait = false;
962 }, 988 };
963 989
964 /** 990 /**
965 * Waits until a function returns true to process a next step. 991 * Waits until a function returns true to process a next step.
966 * 992 *
967 * @param Function testFx A function to be evaluated for returning condition satisfecit 993 * @param Function testFx A function to be evaluated for returning condition satisfecit
...@@ -970,12 +996,12 @@ Casper.prototype = { ...@@ -970,12 +996,12 @@ Casper.prototype = {
970 * @param Number timeout The max amount of time to wait, in milliseconds (optional) 996 * @param Number timeout The max amount of time to wait, in milliseconds (optional)
971 * @return Casper 997 * @return Casper
972 */ 998 */
973 waitFor: function(testFx, then, onTimeout, timeout) { 999 Casper.prototype.waitFor = function(testFx, then, onTimeout, timeout) {
974 timeout = timeout ? timeout : this.defaultWaitTimeout; 1000 timeout = timeout ? timeout : this.defaultWaitTimeout;
975 if (!utils.isType(testFx, "function")) { 1001 if (!utils.isFunction(testFx)) {
976 this.die("waitFor() needs a test function"); 1002 this.die("waitFor() needs a test function");
977 } 1003 }
978 if (then && !utils.isType(then, "function")) { 1004 if (then && !utils.isFunction(then)) {
979 this.die("waitFor() next step definition must be a function"); 1005 this.die("waitFor() next step definition must be a function");
980 } 1006 }
981 return this.then(function(self) { 1007 return this.then(function(self) {
...@@ -989,7 +1015,8 @@ Casper.prototype = { ...@@ -989,7 +1015,8 @@ Casper.prototype = {
989 self.waitDone(); 1015 self.waitDone();
990 if (!condition) { 1016 if (!condition) {
991 self.log("Casper.waitFor() timeout", "warning"); 1017 self.log("Casper.waitFor() timeout", "warning");
992 if (utils.isType(onTimeout, "function")) { 1018 self.emit('casper.waitFor.timeout');
1019 if (utils.isFunction(onTimeout)) {
993 onTimeout.call(self, self); 1020 onTimeout.call(self, self);
994 } else { 1021 } else {
995 self.die("Timeout of " + timeout + "ms expired, exiting.", "error"); 1022 self.die("Timeout of " + timeout + "ms expired, exiting.", "error");
...@@ -1005,9 +1032,9 @@ Casper.prototype = { ...@@ -1005,9 +1032,9 @@ Casper.prototype = {
1005 } 1032 }
1006 }, 100, self, testFx, timeout, onTimeout); 1033 }, 100, self, testFx, timeout, onTimeout);
1007 }); 1034 });
1008 }, 1035 };
1009 1036
1010 /** 1037 /**
1011 * Waits until a given resource is loaded 1038 * Waits until a given resource is loaded
1012 * 1039 *
1013 * @param String/Function test A function to test if the resource exists. A string will be matched against the resources url. 1040 * @param String/Function test A function to test if the resource exists. A string will be matched against the resources url.
...@@ -1016,14 +1043,14 @@ Casper.prototype = { ...@@ -1016,14 +1043,14 @@ Casper.prototype = {
1016 * @param Number timeout The max amount of time to wait, in milliseconds (optional) 1043 * @param Number timeout The max amount of time to wait, in milliseconds (optional)
1017 * @return Casper 1044 * @return Casper
1018 */ 1045 */
1019 waitForResource: function(test, then, onTimeout, timeout) { 1046 Casper.prototype.waitForResource = function(test, then, onTimeout, timeout) {
1020 timeout = timeout ? timeout : this.defaultWaitTimeout; 1047 timeout = timeout ? timeout : this.defaultWaitTimeout;
1021 return this.waitFor(function(self) { 1048 return this.waitFor(function(self) {
1022 return self.resourceExists(test); 1049 return self.resourceExists(test);
1023 }, then, onTimeout, timeout); 1050 }, then, onTimeout, timeout);
1024 }, 1051 };
1025 1052
1026 /** 1053 /**
1027 * Waits until an element matching the provided CSS3 selector exists in 1054 * Waits until an element matching the provided CSS3 selector exists in
1028 * remote DOM to process a next step. 1055 * remote DOM to process a next step.
1029 * 1056 *
...@@ -1033,14 +1060,14 @@ Casper.prototype = { ...@@ -1033,14 +1060,14 @@ Casper.prototype = {
1033 * @param Number timeout The max amount of time to wait, in milliseconds (optional) 1060 * @param Number timeout The max amount of time to wait, in milliseconds (optional)
1034 * @return Casper 1061 * @return Casper
1035 */ 1062 */
1036 waitForSelector: function(selector, then, onTimeout, timeout) { 1063 Casper.prototype.waitForSelector = function(selector, then, onTimeout, timeout) {
1037 timeout = timeout ? timeout : this.defaultWaitTimeout; 1064 timeout = timeout ? timeout : this.defaultWaitTimeout;
1038 return this.waitFor(function(self) { 1065 return this.waitFor(function(self) {
1039 return self.exists(selector); 1066 return self.exists(selector);
1040 }, then, onTimeout, timeout); 1067 }, then, onTimeout, timeout);
1041 }, 1068 };
1042 1069
1043 /** 1070 /**
1044 * Waits until an element matching the provided CSS3 selector does not 1071 * Waits until an element matching the provided CSS3 selector does not
1045 * exist in the remote DOM to process a next step. 1072 * exist in the remote DOM to process a next step.
1046 * 1073 *
...@@ -1050,14 +1077,14 @@ Casper.prototype = { ...@@ -1050,14 +1077,14 @@ Casper.prototype = {
1050 * @param Number timeout The max amount of time to wait, in milliseconds (optional) 1077 * @param Number timeout The max amount of time to wait, in milliseconds (optional)
1051 * @return Casper 1078 * @return Casper
1052 */ 1079 */
1053 waitWhileSelector: function(selector, then, onTimeout, timeout) { 1080 Casper.prototype.waitWhileSelector = function(selector, then, onTimeout, timeout) {
1054 timeout = timeout ? timeout : this.defaultWaitTimeout; 1081 timeout = timeout ? timeout : this.defaultWaitTimeout;
1055 return this.waitFor(function(self) { 1082 return this.waitFor(function(self) {
1056 return !self.exists(selector); 1083 return !self.exists(selector);
1057 }, then, onTimeout, timeout); 1084 }, then, onTimeout, timeout);
1058 }, 1085 };
1059 1086
1060 /** 1087 /**
1061 * Waits until an element matching the provided CSS3 selector is 1088 * Waits until an element matching the provided CSS3 selector is
1062 * visible in the remote DOM to process a next step. 1089 * visible in the remote DOM to process a next step.
1063 * 1090 *
...@@ -1067,14 +1094,14 @@ Casper.prototype = { ...@@ -1067,14 +1094,14 @@ Casper.prototype = {
1067 * @param Number timeout The max amount of time to wait, in milliseconds (optional) 1094 * @param Number timeout The max amount of time to wait, in milliseconds (optional)
1068 * @return Casper 1095 * @return Casper
1069 */ 1096 */
1070 waitUntilVisible: function(selector, then, onTimeout, timeout) { 1097 Casper.prototype.waitUntilVisible = function(selector, then, onTimeout, timeout) {
1071 timeout = timeout ? timeout : this.defaultWaitTimeout; 1098 timeout = timeout ? timeout : this.defaultWaitTimeout;
1072 return this.waitFor(function(self) { 1099 return this.waitFor(function(self) {
1073 return self.visible(selector); 1100 return self.visible(selector);
1074 }, then, onTimeout, timeout); 1101 }, then, onTimeout, timeout);
1075 }, 1102 };
1076 1103
1077 /** 1104 /**
1078 * Waits until an element matching the provided CSS3 selector is no 1105 * Waits until an element matching the provided CSS3 selector is no
1079 * longer visible in remote DOM to process a next step. 1106 * longer visible in remote DOM to process a next step.
1080 * 1107 *
...@@ -1084,12 +1111,11 @@ Casper.prototype = { ...@@ -1084,12 +1111,11 @@ Casper.prototype = {
1084 * @param Number timeout The max amount of time to wait, in milliseconds (optional) 1111 * @param Number timeout The max amount of time to wait, in milliseconds (optional)
1085 * @return Casper 1112 * @return Casper
1086 */ 1113 */
1087 waitWhileVisible: function(selector, then, onTimeout, timeout) { 1114 Casper.prototype.waitWhileVisible = function(selector, then, onTimeout, timeout) {
1088 timeout = timeout ? timeout : this.defaultWaitTimeout; 1115 timeout = timeout ? timeout : this.defaultWaitTimeout;
1089 return this.waitFor(function(self) { 1116 return this.waitFor(function(self) {
1090 return !self.visible(selector); 1117 return !self.visible(selector);
1091 }, then, onTimeout, timeout); 1118 }, then, onTimeout, timeout);
1092 }
1093 }; 1119 };
1094 1120
1095 /** 1121 /**
...@@ -1098,7 +1124,7 @@ Casper.prototype = { ...@@ -1098,7 +1124,7 @@ Casper.prototype = {
1098 * @param Object proto Prototype methods to add to Casper 1124 * @param Object proto Prototype methods to add to Casper
1099 */ 1125 */
1100 Casper.extend = function(proto) { 1126 Casper.extend = function(proto) {
1101 if (!utils.isType(proto, "object")) { 1127 if (!utils.isObject(proto)) {
1102 throw new Error("extends() only accept objects as prototypes"); 1128 throw new Error("extends() only accept objects as prototypes");
1103 } 1129 }
1104 utils.mergeObjects(Casper.prototype, proto); 1130 utils.mergeObjects(Casper.prototype, proto);
...@@ -1114,14 +1140,15 @@ exports.Casper = Casper; ...@@ -1114,14 +1140,15 @@ exports.Casper = Casper;
1114 */ 1140 */
1115 function createPage(casper) { 1141 function createPage(casper) {
1116 var page; 1142 var page;
1117 if (phantom.version.major <= 1 && phantom.version.minor < 3 && utils.isType(require, "function")) { 1143 if (phantom.version.major <= 1 && phantom.version.minor < 3 && utils.isFunction(require)) {
1118 page = new WebPage(); 1144 page = new WebPage();
1119 } else { 1145 } else {
1120 page = require('webpage').create(); 1146 page = require('webpage').create();
1121 } 1147 }
1122 page.onAlert = function(message) { 1148 page.onAlert = function(message) {
1123 casper.log('[alert] ' + message, "info", "remote"); 1149 casper.log('[alert] ' + message, "info", "remote");
1124 if (utils.isType(casper.options.onAlert, "function")) { 1150 casper.emit('casper.remote.alert', message);
1151 if (utils.isFunction(casper.options.onAlert)) {
1125 casper.options.onAlert.call(casper, casper, message); 1152 casper.options.onAlert.call(casper, casper, message);
1126 } 1153 }
1127 }; 1154 };
...@@ -1132,25 +1159,32 @@ function createPage(casper) { ...@@ -1132,25 +1159,32 @@ function createPage(casper) {
1132 msg = test[2]; 1159 msg = test[2];
1133 } 1160 }
1134 casper.log(msg, level, "remote"); 1161 casper.log(msg, level, "remote");
1162 casper.emit('casper.remote.message', msg);
1135 }; 1163 };
1136 page.onLoadStarted = function() { 1164 page.onLoadStarted = function() {
1137 casper.resources = [];
1138 casper.loadInProgress = true; 1165 casper.loadInProgress = true;
1166 casper.resources = [];
1167 casper.emit('casper.load.started');
1139 }; 1168 };
1140 page.onLoadFinished = function(status) { 1169 page.onLoadFinished = function(status) {
1141 if (status !== "success") { 1170 if (status !== "success") {
1171 casper.emit('casper.load.failed', {
1172 status: status,
1173 http_status: casper.currentHTTPStatus,
1174 url: casper.requestUrl
1175 });
1142 var message = 'Loading resource failed with status=' + status; 1176 var message = 'Loading resource failed with status=' + status;
1143 if (casper.currentHTTPStatus) { 1177 if (casper.currentHTTPStatus) {
1144 message += ' (HTTP ' + casper.currentHTTPStatus + ')'; 1178 message += ' (HTTP ' + casper.currentHTTPStatus + ')';
1145 } 1179 }
1146 message += ': ' + casper.requestUrl; 1180 message += ': ' + casper.requestUrl;
1147 casper.log(message, "warning"); 1181 casper.log(message, "warning");
1148 if (utils.isType(casper.options.onLoadError, "function")) { 1182 if (utils.isFunction(casper.options.onLoadError)) {
1149 casper.options.onLoadError.call(casper, casper, casper.requestUrl, status); 1183 casper.options.onLoadError.call(casper, casper, casper.requestUrl, status);
1150 } 1184 }
1151 } 1185 }
1152 if (casper.options.clientScripts) { 1186 if (casper.options.clientScripts) {
1153 if (!utils.isType(casper.options.clientScripts, "array")) { 1187 if (!utils.isArray(casper.options.clientScripts)) {
1154 casper.log("The clientScripts option must be an array", "error"); 1188 casper.log("The clientScripts option must be an array", "error");
1155 } else { 1189 } else {
1156 for (var i = 0; i < casper.options.clientScripts.length; i++) { 1190 for (var i = 0; i < casper.options.clientScripts.length; i++) {
...@@ -1172,10 +1206,12 @@ function createPage(casper) { ...@@ -1172,10 +1206,12 @@ function createPage(casper) {
1172 } 1206 }
1173 // history 1207 // history
1174 casper.history.push(casper.getCurrentUrl()); 1208 casper.history.push(casper.getCurrentUrl());
1209 casper.emit('casper.load.finished', status);
1175 casper.loadInProgress = false; 1210 casper.loadInProgress = false;
1176 }; 1211 };
1177 page.onResourceReceived = function(resource) { 1212 page.onResourceReceived = function(resource) {
1178 if (utils.isType(casper.options.onResourceReceived, "function")) { 1213 casper.emit('casper.resource.received', resource);
1214 if (utils.isFunction(casper.options.onResourceReceived)) {
1179 casper.options.onResourceReceived.call(casper, casper, resource); 1215 casper.options.onResourceReceived.call(casper, casper, resource);
1180 } 1216 }
1181 if (resource.stage === "end") { 1217 if (resource.stage === "end") {
...@@ -1183,18 +1219,21 @@ function createPage(casper) { ...@@ -1183,18 +1219,21 @@ function createPage(casper) {
1183 } 1219 }
1184 if (resource.url === casper.requestUrl && resource.stage === "start") { 1220 if (resource.url === casper.requestUrl && resource.stage === "start") {
1185 casper.currentHTTPStatus = resource.status; 1221 casper.currentHTTPStatus = resource.status;
1186 if (utils.isType(casper.options.httpStatusHandlers, "object") && 1222 casper.emit('casper.http.status.' + resource.status, resource);
1223 if (utils.isObject(casper.options.httpStatusHandlers) &&
1187 resource.status in casper.options.httpStatusHandlers && 1224 resource.status in casper.options.httpStatusHandlers &&
1188 utils.isType(casper.options.httpStatusHandlers[resource.status], "function")) { 1225 utils.isFunction(casper.options.httpStatusHandlers[resource.status])) {
1189 casper.options.httpStatusHandlers[resource.status].call(casper, casper, resource); 1226 casper.options.httpStatusHandlers[resource.status].call(casper, casper, resource);
1190 } 1227 }
1191 casper.currentUrl = resource.url; 1228 casper.currentUrl = resource.url;
1192 } 1229 }
1193 }; 1230 };
1194 page.onResourceRequested = function(request) { 1231 page.onResourceRequested = function(request) {
1195 if (utils.isType(casper.options.onResourceRequested, "function")) { 1232 casper.emit('casper.resource.requested', request);
1233 if (utils.isFunction(casper.options.onResourceRequested)) {
1196 casper.options.onResourceRequested.call(casper, casper, request); 1234 casper.options.onResourceRequested.call(casper, casper, request);
1197 } 1235 }
1198 }; 1236 };
1237 casper.emit('casper.page.created', page);
1199 return page; 1238 return page;
1200 } 1239 }
......
...@@ -70,7 +70,7 @@ function fileExt(file) { ...@@ -70,7 +70,7 @@ function fileExt(file) {
70 exports.fileExt = fileExt; 70 exports.fileExt = fileExt;
71 71
72 /** 72 /**
73 * Takes a string and append blank until the pad value is reached. 73 * Takes a string and append blanks until the pad value is reached.
74 * 74 *
75 * @param String text 75 * @param String text
76 * @param Number pad Pad value (optional; default: 80) 76 * @param Number pad Pad value (optional; default: 80)
...@@ -85,8 +85,14 @@ function fillBlanks(text, pad) { ...@@ -85,8 +85,14 @@ function fillBlanks(text, pad) {
85 } 85 }
86 exports.fillBlanks = fillBlanks; 86 exports.fillBlanks = fillBlanks;
87 87
88 /**
89 * Checks if value is a javascript Array
90 *
91 * @param mixed value
92 * @return Boolean
93 */
88 function isArray(value) { 94 function isArray(value) {
89 return isType(value, "array"); 95 return Array.isArray(value) || isType(value, "array");
90 } 96 }
91 exports.isArray = isArray; 97 exports.isArray = isArray;
92 98
...@@ -101,17 +107,27 @@ function isCasperObject(value) { ...@@ -101,17 +107,27 @@ function isCasperObject(value) {
101 } 107 }
102 exports.isCasperObject = isCasperObject; 108 exports.isCasperObject = isCasperObject;
103 109
110 /**
111 * Checks if value is a phantomjs clipRect-compatible object
112 *
113 * @param mixed value
114 * @return Boolean
115 */
104 function isClipRect(value) { 116 function isClipRect(value) {
105 return isType(value, "cliprect") || ( 117 return isType(value, "cliprect") || (
106 isType(value, "object") && 118 isObject(value) &&
107 isType(value.top, "number") && 119 isNumber(value.top) && isNumber(value.left) &&
108 isType(value.left, "number") && 120 isNumber(value.width) && isNumber(value.height)
109 isType(value.width, "number") &&
110 isType(value.height, "number")
111 ); 121 );
112 } 122 }
113 exports.isClipRect = isClipRect; 123 exports.isClipRect = isClipRect;
114 124
125 /**
126 * Checks if value is a javascript Function
127 *
128 * @param mixed value
129 * @return Boolean
130 */
115 function isFunction(value) { 131 function isFunction(value) {
116 return isType(value, "function"); 132 return isType(value, "function");
117 } 133 }
...@@ -125,15 +141,38 @@ exports.isFunction = isFunction; ...@@ -125,15 +141,38 @@ exports.isFunction = isFunction;
125 */ 141 */
126 function isJsFile(file) { 142 function isJsFile(file) {
127 var ext = fileExt(file); 143 var ext = fileExt(file);
128 return isType(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1; 144 return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1;
129 } 145 }
130 exports.isJsFile = isJsFile; 146 exports.isJsFile = isJsFile;
131 147
148 /**
149 * Checks if value is a javascript Number
150 *
151 * @param mixed value
152 * @return Boolean
153 */
154 function isNumber(value) {
155 return isType(value, "number");
156 }
157 exports.isNumber = isNumber;
158
159 /**
160 * Checks if value is a javascript Object
161 *
162 * @param mixed value
163 * @return Boolean
164 */
132 function isObject(value) { 165 function isObject(value) {
133 return isType(value, "object"); 166 return isType(value, "object");
134 } 167 }
135 exports.isObject = isObject; 168 exports.isObject = isObject;
136 169
170 /**
171 * Checks if value is a javascript String
172 *
173 * @param mixed value
174 * @return Boolean
175 */
137 function isString(value) { 176 function isString(value) {
138 return isType(value, "string"); 177 return isType(value, "string");
139 } 178 }
...@@ -148,7 +187,10 @@ exports.isString = isString; ...@@ -148,7 +187,10 @@ exports.isString = isString;
148 * @return Boolean 187 * @return Boolean
149 */ 188 */
150 function isType(what, typeName) { 189 function isType(what, typeName) {
151 return betterTypeOf(what) === typeName; 190 if (typeof typeName !== "string" || !typeName) {
191 throw new Error("You must pass isType() a typeName string");
192 }
193 return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
152 } 194 }
153 exports.isType = isType; 195 exports.isType = isType;
154 196
...@@ -159,10 +201,10 @@ exports.isType = isType; ...@@ -159,10 +201,10 @@ exports.isType = isType;
159 * @return Boolean 201 * @return Boolean
160 */ 202 */
161 function isWebPage(what) { 203 function isWebPage(what) {
162 if (!what || !isType(what, "object")) { 204 if (!what || !isObject(what)) {
163 return false; 205 return false;
164 } 206 }
165 if (phantom.version.major <= 1 && phantom.version.minor < 3 && isType(require, "function")) { 207 if (phantom.version.major <= 1 && phantom.version.minor < 3 && isFunction(require)) {
166 return what instanceof WebPage; 208 return what instanceof WebPage;
167 } else { 209 } else {
168 return what.toString().indexOf('WebPage(') === 0; 210 return what.toString().indexOf('WebPage(') === 0;
...@@ -202,9 +244,34 @@ exports.mergeObjects = mergeObjects; ...@@ -202,9 +244,34 @@ exports.mergeObjects = mergeObjects;
202 function serialize(value) { 244 function serialize(value) {
203 if (isType(value, "array")) { 245 if (isType(value, "array")) {
204 value = value.map(function(prop) { 246 value = value.map(function(prop) {
205 return isType(prop, "function") ? prop.toString().replace(/\s{2,}/, '') : prop; 247 return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop;
206 }); 248 });
207 } 249 }
208 return JSON.stringify(value, null, 4); 250 return JSON.stringify(value, null, 4);
209 } 251 }
210 exports.serialize = serialize; 252 exports.serialize = serialize;
253
254 /**
255 * Inherit the prototype methods from one constructor into another.
256 *
257 * The Function.prototype.inherits from lang.js rewritten as a standalone
258 * function (not on Function.prototype). NOTE: If this file is to be loaded
259 * during bootstrapping this function needs to be revritten using some native
260 * functions as prototype setup using normal JavaScript does not work as
261 * expected during bootstrapping (see mirror.js in r114903).
262 *
263 * @param {function} ctor Constructor function which needs to inherit the
264 * prototype.
265 * @param {function} superCtor Constructor function to inherit prototype from.
266 */
267 exports.inherits = function(ctor, superCtor) {
268 ctor.super_ = superCtor;
269 ctor.prototype = Object.create(superCtor.prototype, {
270 constructor: {
271 value: ctor,
272 enumerable: false,
273 writable: true,
274 configurable: true
275 }
276 });
277 };
......