phantomjs-binaries 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/modules/tester.js ADDED
@@ -0,0 +1,1161 @@
1
+ /*!
2
+ * Casper is a navigation utility for PhantomJS.
3
+ *
4
+ * Documentation: http://casperjs.org/
5
+ * Repository: http://github.com/n1k0/casperjs
6
+ *
7
+ * Copyright (c) 2011-2012 Nicolas Perriault
8
+ *
9
+ * Part of source code is Copyright Joyent, Inc. and other Node contributors.
10
+ *
11
+ * Permission is hereby granted, free of charge, to any person obtaining a
12
+ * copy of this software and associated documentation files (the "Software"),
13
+ * to deal in the Software without restriction, including without limitation
14
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
+ * and/or sell copies of the Software, and to permit persons to whom the
16
+ * Software is furnished to do so, subject to the following conditions:
17
+ *
18
+ * The above copyright notice and this permission notice shall be included
19
+ * in all copies or substantial portions of the Software.
20
+ *
21
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
+ * DEALINGS IN THE SOFTWARE.
28
+ *
29
+ */
30
+
31
+ /*global CasperError exports phantom require __utils__*/
32
+
33
+ var fs = require('fs');
34
+ var events = require('events');
35
+ var utils = require('utils');
36
+ var f = utils.format;
37
+
38
+ exports.create = function create(casper, options) {
39
+ "use strict";
40
+ return new Tester(casper, options);
41
+ };
42
+
43
+ /**
44
+ * Casper tester: makes assertions, stores test results and display then.
45
+ *
46
+ * @param Casper casper A valid Casper instance
47
+ * @param Object|null options Options object
48
+ */
49
+ var Tester = function Tester(casper, options) {
50
+ "use strict";
51
+ /*jshint maxstatements:30*/
52
+
53
+ if (!utils.isCasperObject(casper)) {
54
+ throw new CasperError("Tester needs a Casper instance");
55
+ }
56
+
57
+ var self = this;
58
+
59
+ this.casper = casper;
60
+
61
+ this.SKIP_MESSAGE = '__termination__';
62
+
63
+ this.aborted = false;
64
+ this.executed = 0;
65
+ this.currentTestFile = null;
66
+ this.currentSuiteNum = 0;
67
+ this.exporter = require('xunit').create();
68
+ this.loadIncludes = {
69
+ includes: [],
70
+ pre: [],
71
+ post: []
72
+ };
73
+ this.running = false;
74
+ this.suites = [];
75
+ this.options = utils.mergeObjects({
76
+ failFast: false, // terminates a suite as soon as a test fails?
77
+ failText: "FAIL", // text to use for a successful test
78
+ passText: "PASS", // text to use for a failed test
79
+ pad: 80 , // maximum number of chars for a result line
80
+ warnText: "WARN" // text to use for a dubious test
81
+ }, options);
82
+
83
+ // properties
84
+ this.testResults = {
85
+ passed: 0,
86
+ failed: 0,
87
+ passes: [],
88
+ failures: [],
89
+ passesTime: [],
90
+ failuresTime: []
91
+ };
92
+
93
+ // measuring test duration
94
+ this.currentTestStartTime = null;
95
+ this.lastAssertTime = 0;
96
+
97
+ this.configure();
98
+
99
+ this.on('success', function onSuccess(success) {
100
+ this.testResults.passes.push(success);
101
+ var timeElapsed = new Date() - this.currentTestStartTime;
102
+ this.testResults.passesTime.push(timeElapsed - this.lastAssertTime);
103
+ this.exporter.addSuccess(fs.absolute(success.file), success.message || success.standard, timeElapsed - this.lastAssertTime);
104
+ this.lastAssertTime = timeElapsed;
105
+ });
106
+
107
+ this.on('fail', function onFail(failure) {
108
+ // export
109
+ var timeElapsed = new Date() - this.currentTestStartTime;
110
+ this.testResults.failuresTime.push(timeElapsed - this.lastAssertTime);
111
+ this.exporter.addFailure(
112
+ fs.absolute(failure.file),
113
+ failure.message || failure.standard,
114
+ failure.standard || "test failed",
115
+ failure.type || "unknown",
116
+ (timeElapsed - this.lastAssertTime)
117
+ );
118
+ this.lastAssertTime = timeElapsed;
119
+ this.testResults.failures.push(failure);
120
+
121
+ // special printing
122
+ if (failure.type) {
123
+ this.comment(' type: ' + failure.type);
124
+ }
125
+ if (failure.values && Object.keys(failure.values).length > 0) {
126
+ for (var name in failure.values) {
127
+ var comment = ' ' + name + ': ';
128
+ var value = failure.values[name];
129
+ try {
130
+ comment += utils.serialize(failure.values[name]);
131
+ } catch (e) {
132
+ try {
133
+ comment += utils.serialize(failure.values[name].toString());
134
+ } catch (e2) {
135
+ comment += '(unserializable value)';
136
+ }
137
+ }
138
+ this.comment(comment);
139
+ }
140
+ }
141
+ });
142
+
143
+ // casper events
144
+ this.casper.on('error', function onCasperError(msg, backtrace) {
145
+ if (!phantom.casperTest) {
146
+ return;
147
+ }
148
+ if (msg === self.SKIP_MESSAGE) {
149
+ this.warn(f('--fail-fast: aborted remaining tests in "%s"', self.currentTestFile));
150
+ self.aborted = true;
151
+ return self.done();
152
+ }
153
+ var line = 0;
154
+ if (!utils.isString(msg)) {
155
+ try {
156
+ line = backtrace[0].line;
157
+ } catch (e) {}
158
+ }
159
+ self.uncaughtError(msg, self.currentTestFile, line);
160
+ self.done();
161
+ });
162
+
163
+ this.casper.on('step.error', function onStepError(e) {
164
+ if (e.message !== self.SKIP_MESSAGE) {
165
+ self.uncaughtError(e, self.currentTestFile);
166
+ }
167
+ self.done();
168
+ });
169
+ };
170
+
171
+ // Tester class is an EventEmitter
172
+ utils.inherits(Tester, events.EventEmitter);
173
+ exports.Tester = Tester;
174
+
175
+ /**
176
+ * Asserts that a condition strictly resolves to true. Also returns an
177
+ * "assertion object" containing useful informations about the test case
178
+ * results.
179
+ *
180
+ * This method is also used as the base one used for all other `assert*`
181
+ * family methods; supplementary informations are then passed using the
182
+ * `context` argument.
183
+ *
184
+ * @param Boolean subject The condition to test
185
+ * @param String message Test description
186
+ * @param Object|null context Assertion context object (Optional)
187
+ * @return Object An assertion result object
188
+ */
189
+ Tester.prototype.assert = Tester.prototype.assertTrue = function assert(subject, message, context) {
190
+ "use strict";
191
+ this.executed++;
192
+ return this.processAssertionResult(utils.mergeObjects({
193
+ success: subject === true,
194
+ type: "assert",
195
+ standard: "Subject is strictly true",
196
+ message: message,
197
+ file: this.currentTestFile,
198
+ values: {
199
+ subject: utils.getPropertyPath(context, 'values.subject') || subject
200
+ }
201
+ }, context || {}));
202
+ };
203
+
204
+ /**
205
+ * Asserts that two values are strictly equals.
206
+ *
207
+ * @param Mixed subject The value to test
208
+ * @param Mixed expected The expected value
209
+ * @param String message Test description (Optional)
210
+ * @return Object An assertion result object
211
+ */
212
+ Tester.prototype.assertEquals = Tester.prototype.assertEqual = function assertEquals(subject, expected, message) {
213
+ "use strict";
214
+ return this.assert(this.testEquals(subject, expected), message, {
215
+ type: "assertEquals",
216
+ standard: "Subject equals the expected value",
217
+ values: {
218
+ subject: subject,
219
+ expected: expected
220
+ }
221
+ });
222
+ };
223
+
224
+ /**
225
+ * Asserts that two values are strictly not equals.
226
+ *
227
+ * @param Mixed subject The value to test
228
+ * @param Mixed expected The unwanted value
229
+ * @param String|null message Test description (Optional)
230
+ * @return Object An assertion result object
231
+ */
232
+ Tester.prototype.assertNotEquals = function assertNotEquals(subject, shouldnt, message) {
233
+ "use strict";
234
+ return this.assert(!this.testEquals(subject, shouldnt), message, {
235
+ type: "assertNotEquals",
236
+ standard: "Subject doesn't equal what it shouldn't be",
237
+ values: {
238
+ subject: subject,
239
+ shouldnt: shouldnt
240
+ }
241
+ });
242
+ };
243
+
244
+ /**
245
+ * Asserts that a code evaluation in remote DOM resolves to true.
246
+ *
247
+ * @param Function fn A function to be evaluated in remote DOM
248
+ * @param String message Test description
249
+ * @param Object params Object/Array containing the parameters to inject into the function (optional)
250
+ * @return Object An assertion result object
251
+ */
252
+ Tester.prototype.assertEval = Tester.prototype.assertEvaluate = function assertEval(fn, message, params) {
253
+ "use strict";
254
+ return this.assert(this.casper.evaluate(fn, params), message, {
255
+ type: "assertEval",
256
+ standard: "Evaluated function returns true",
257
+ values: {
258
+ fn: fn,
259
+ params: params
260
+ }
261
+ });
262
+ };
263
+
264
+ /**
265
+ * Asserts that the result of a code evaluation in remote DOM equals
266
+ * an expected value.
267
+ *
268
+ * @param Function fn The function to be evaluated in remote DOM
269
+ * @param Boolean expected The expected value
270
+ * @param String|null message Test description
271
+ * @param Object|null params Object containing the parameters to inject into the function (optional)
272
+ * @return Object An assertion result object
273
+ */
274
+ Tester.prototype.assertEvalEquals = Tester.prototype.assertEvalEqual = function assertEvalEquals(fn, expected, message, params) {
275
+ "use strict";
276
+ var subject = this.casper.evaluate(fn, params);
277
+ return this.assert(this.testEquals(subject, expected), message, {
278
+ type: "assertEvalEquals",
279
+ standard: "Evaluated function returns the expected value",
280
+ values: {
281
+ fn: fn,
282
+ params: params,
283
+ subject: subject,
284
+ expected: expected
285
+ }
286
+ });
287
+ };
288
+
289
+ /**
290
+ * Asserts that a given input field has the provided value.
291
+ *
292
+ * @param String inputName The name attribute of the input element
293
+ * @param String expected The expected value of the input element
294
+ * @param String message Test description
295
+ * @return Object An assertion result object
296
+ */
297
+ Tester.prototype.assertField = function assertField(inputName, expected, message) {
298
+ "use strict";
299
+ var actual = this.casper.evaluate(function(inputName) {
300
+ return __utils__.getFieldValue(inputName);
301
+ }, inputName);
302
+ return this.assert(this.testEquals(actual, expected), message, {
303
+ type: 'assertField',
304
+ standard: f('"%s" input field has the value "%s"', inputName, expected),
305
+ values: {
306
+ inputName: inputName,
307
+ actual: actual,
308
+ expected: expected
309
+ }
310
+ });
311
+ };
312
+
313
+ /**
314
+ * Asserts that an element matching the provided selector expression exists in
315
+ * remote DOM.
316
+ *
317
+ * @param String selector Selector expression
318
+ * @param String message Test description
319
+ * @return Object An assertion result object
320
+ */
321
+ Tester.prototype.assertExists = Tester.prototype.assertExist = Tester.prototype.assertSelectorExists = Tester.prototype.assertSelectorExist = function assertExists(selector, message) {
322
+ "use strict";
323
+ return this.assert(this.casper.exists(selector), message, {
324
+ type: "assertExists",
325
+ standard: f("Found an element matching: %s", selector),
326
+ values: {
327
+ selector: selector
328
+ }
329
+ });
330
+ };
331
+
332
+ /**
333
+ * Asserts that an element matching the provided selector expression does not
334
+ * exists in remote DOM.
335
+ *
336
+ * @param String selector Selector expression
337
+ * @param String message Test description
338
+ * @return Object An assertion result object
339
+ */
340
+ Tester.prototype.assertDoesntExist = Tester.prototype.assertNotExists = function assertDoesntExist(selector, message) {
341
+ "use strict";
342
+ return this.assert(!this.casper.exists(selector), message, {
343
+ type: "assertDoesntExist",
344
+ standard: f("No element found matching selector: %s", selector),
345
+ values: {
346
+ selector: selector
347
+ }
348
+ });
349
+ };
350
+
351
+ /**
352
+ * Asserts that current HTTP status is the one passed as argument.
353
+ *
354
+ * @param Number status HTTP status code
355
+ * @param String message Test description
356
+ * @return Object An assertion result object
357
+ */
358
+ Tester.prototype.assertHttpStatus = function assertHttpStatus(status, message) {
359
+ "use strict";
360
+ var currentHTTPStatus = this.casper.currentHTTPStatus;
361
+ return this.assert(this.testEquals(this.casper.currentHTTPStatus, status), message, {
362
+ type: "assertHttpStatus",
363
+ standard: f("HTTP status code is: %s", status),
364
+ values: {
365
+ current: currentHTTPStatus,
366
+ expected: status
367
+ }
368
+ });
369
+ };
370
+
371
+ /**
372
+ * Asserts that a provided string matches a provided RegExp pattern.
373
+ *
374
+ * @param String subject The string to test
375
+ * @param RegExp pattern A RegExp object instance
376
+ * @param String message Test description
377
+ * @return Object An assertion result object
378
+ */
379
+ Tester.prototype.assertMatch = Tester.prototype.assertMatches = function assertMatch(subject, pattern, message) {
380
+ "use strict";
381
+ if (utils.betterTypeOf(pattern) !== "regexp") {
382
+ throw new CasperError('Invalid regexp.');
383
+ }
384
+ return this.assert(pattern.test(subject), message, {
385
+ type: "assertMatch",
386
+ standard: "Subject matches the provided pattern",
387
+ values: {
388
+ subject: subject,
389
+ pattern: pattern.toString()
390
+ }
391
+ });
392
+ };
393
+
394
+ /**
395
+ * Asserts a condition resolves to false.
396
+ *
397
+ * @param Boolean condition The condition to test
398
+ * @param String message Test description
399
+ * @return Object An assertion result object
400
+ */
401
+ Tester.prototype.assertNot = Tester.prototype.assertFalse = function assertNot(condition, message) {
402
+ "use strict";
403
+ return this.assert(!condition, message, {
404
+ type: "assertNot",
405
+ standard: "Subject is falsy",
406
+ values: {
407
+ condition: condition
408
+ }
409
+ });
410
+ };
411
+
412
+ /**
413
+ * Asserts that a selector expression is not currently visible.
414
+ *
415
+ * @param String expected selector expression
416
+ * @param String message Test description
417
+ * @return Object An assertion result object
418
+ */
419
+ Tester.prototype.assertNotVisible = Tester.prototype.assertInvisible = function assertNotVisible(selector, message) {
420
+ "use strict";
421
+ return this.assert(!this.casper.visible(selector), message, {
422
+ type: "assertVisible",
423
+ standard: "Selector is not visible",
424
+ values: {
425
+ selector: selector
426
+ }
427
+ });
428
+ };
429
+
430
+ /**
431
+ * Asserts that the provided function called with the given parameters
432
+ * will raise an exception.
433
+ *
434
+ * @param Function fn The function to test
435
+ * @param Array args The arguments to pass to the function
436
+ * @param String message Test description
437
+ * @return Object An assertion result object
438
+ */
439
+ Tester.prototype.assertRaises = Tester.prototype.assertRaise = Tester.prototype.assertThrows = function assertRaises(fn, args, message) {
440
+ "use strict";
441
+ var context = {
442
+ type: "assertRaises",
443
+ standard: "Function raises an error"
444
+ };
445
+ try {
446
+ fn.apply(null, args);
447
+ this.assert(false, message, context);
448
+ } catch (error) {
449
+ this.assert(true, message, utils.mergeObjects(context, {
450
+ values: {
451
+ error: error
452
+ }
453
+ }));
454
+ }
455
+ };
456
+
457
+ /**
458
+ * Asserts that the current page has a resource that matches the provided test
459
+ *
460
+ * @param Function/String test A test function that is called with every response
461
+ * @param String message Test description
462
+ * @return Object An assertion result object
463
+ */
464
+ Tester.prototype.assertResourceExists = Tester.prototype.assertResourceExist = function assertResourceExists(test, message) {
465
+ "use strict";
466
+ return this.assert(this.casper.resourceExists(test), message, {
467
+ type: "assertResourceExists",
468
+ standard: "Expected resource has been found",
469
+ values: {
470
+ test: test
471
+ }
472
+ });
473
+ };
474
+
475
+ /**
476
+ * Asserts that given text doesn't exist in the document body.
477
+ *
478
+ * @param String text Text not to be found
479
+ * @param String message Test description
480
+ * @return Object An assertion result object
481
+ */
482
+ Tester.prototype.assertTextDoesntExist = Tester.prototype.assertTextDoesntExist = function assertTextDoesntExist(text, message) {
483
+ "use strict";
484
+ var textFound = (this.casper.evaluate(function _evaluate() {
485
+ return document.body.textContent || document.body.innerText;
486
+ }).indexOf(text) === -1);
487
+ return this.assert(textFound, message, {
488
+ type: "assertTextDoesntExists",
489
+ standard: "Text doesn't exist within the document body",
490
+ values: {
491
+ text: text
492
+ }
493
+ });
494
+ };
495
+
496
+ /**
497
+ * Asserts that given text exists in the document body.
498
+ *
499
+ * @param String text Text to be found
500
+ * @param String message Test description
501
+ * @return Object An assertion result object
502
+ */
503
+ Tester.prototype.assertTextExists = Tester.prototype.assertTextExist = function assertTextExists(text, message) {
504
+ "use strict";
505
+ var textFound = (this.casper.evaluate(function _evaluate() {
506
+ return document.body.textContent || document.body.innerText;
507
+ }).indexOf(text) !== -1);
508
+ return this.assert(textFound, message, {
509
+ type: "assertTextExists",
510
+ standard: "Found expected text within the document body",
511
+ values: {
512
+ text: text
513
+ }
514
+ });
515
+ };
516
+
517
+ /**
518
+ * Asserts a subject is truthy.
519
+ *
520
+ * @param Mixed subject Test subject
521
+ * @param String message Test description
522
+ * @return Object An assertion result object
523
+ */
524
+ Tester.prototype.assertTruthy = function assertTruthy(subject, message) {
525
+ "use strict";
526
+ /*jshint eqeqeq:false*/
527
+ return this.assert(utils.isTruthy(subject), message, {
528
+ type: "assertTruthy",
529
+ standard: "Subject is truthy",
530
+ values: {
531
+ subject: subject
532
+ }
533
+ });
534
+ };
535
+
536
+ /**
537
+ * Asserts a subject is falsy.
538
+ *
539
+ * @param Mixed subject Test subject
540
+ * @param String message Test description
541
+ * @return Object An assertion result object
542
+ */
543
+ Tester.prototype.assertFalsy = function assertFalsy(subject, message) {
544
+ "use strict";
545
+ /*jshint eqeqeq:false*/
546
+ return this.assert(utils.isFalsy(subject), message, {
547
+ type: "assertFalsy",
548
+ standard: "Subject is falsy",
549
+ values: {
550
+ subject: subject
551
+ }
552
+ });
553
+ };
554
+
555
+ /**
556
+ * Asserts that given text exists in the provided selector.
557
+ *
558
+ * @param String selector Selector expression
559
+ * @param String text Text to be found
560
+ * @param String message Test description
561
+ * @return Object An assertion result object
562
+ */
563
+ Tester.prototype.assertSelectorHasText = Tester.prototype.assertSelectorContains = function assertSelectorHasText(selector, text, message) {
564
+ "use strict";
565
+ var textFound = this.casper.fetchText(selector).indexOf(text) !== -1;
566
+ return this.assert(textFound, message, {
567
+ type: "assertSelectorHasText",
568
+ standard: f('Found "%s" within the selector "%s"', text, selector),
569
+ values: {
570
+ selector: selector,
571
+ text: text
572
+ }
573
+ });
574
+ };
575
+
576
+ /**
577
+ * Asserts that given text does not exist in the provided selector.
578
+ *
579
+ * @param String selector Selector expression
580
+ * @param String text Text not to be found
581
+ * @param String message Test description
582
+ * @return Object An assertion result object
583
+ */
584
+ Tester.prototype.assertSelectorDoesntHaveText = Tester.prototype.assertSelectorDoesntContain = function assertSelectorDoesntHaveText(selector, text, message) {
585
+ "use strict";
586
+ var textFound = this.casper.fetchText(selector).indexOf(text) === -1;
587
+ return this.assert(textFound, message, {
588
+ type: "assertSelectorDoesntHaveText",
589
+ standard: f('Did not find "%s" within the selector "%s"', text, selector),
590
+ values: {
591
+ selector: selector,
592
+ text: text
593
+ }
594
+ });
595
+ };
596
+
597
+ /**
598
+ * Asserts that title of the remote page equals to the expected one.
599
+ *
600
+ * @param String expected The expected title string
601
+ * @param String message Test description
602
+ * @return Object An assertion result object
603
+ */
604
+ Tester.prototype.assertTitle = function assertTitle(expected, message) {
605
+ "use strict";
606
+ var currentTitle = this.casper.getTitle();
607
+ return this.assert(this.testEquals(currentTitle, expected), message, {
608
+ type: "assertTitle",
609
+ standard: f('Page title is: "%s"', expected),
610
+ values: {
611
+ subject: currentTitle,
612
+ expected: expected
613
+ }
614
+ });
615
+ };
616
+
617
+ /**
618
+ * Asserts that title of the remote page matched the provided pattern.
619
+ *
620
+ * @param RegExp pattern The pattern to test the title against
621
+ * @param String message Test description
622
+ * @return Object An assertion result object
623
+ */
624
+ Tester.prototype.assertTitleMatch = Tester.prototype.assertTitleMatches = function assertTitleMatch(pattern, message) {
625
+ "use strict";
626
+ if (utils.betterTypeOf(pattern) !== "regexp") {
627
+ throw new CasperError('Invalid regexp.');
628
+ }
629
+ var currentTitle = this.casper.getTitle();
630
+ return this.assert(pattern.test(currentTitle), message, {
631
+ type: "assertTitle",
632
+ details: "Page title does not match the provided pattern",
633
+ values: {
634
+ subject: currentTitle,
635
+ pattern: pattern.toString()
636
+ }
637
+ });
638
+ };
639
+
640
+ /**
641
+ * Asserts that the provided subject is of the given type.
642
+ *
643
+ * @param mixed subject The value to test
644
+ * @param String type The javascript type name
645
+ * @param String message Test description
646
+ * @return Object An assertion result object
647
+ */
648
+ Tester.prototype.assertType = function assertType(subject, type, message) {
649
+ "use strict";
650
+ var actual = utils.betterTypeOf(subject);
651
+ return this.assert(this.testEquals(actual, type), message, {
652
+ type: "assertType",
653
+ standard: f('Subject type is: "%s"', type),
654
+ values: {
655
+ subject: subject,
656
+ type: type,
657
+ actual: actual
658
+ }
659
+ });
660
+ };
661
+
662
+ /**
663
+ * Asserts that a the current page url matches a given pattern. A pattern may be
664
+ * either a RegExp object or a String. The method will test if the URL matches
665
+ * the pattern or contains the String.
666
+ *
667
+ * @param RegExp|String pattern The test pattern
668
+ * @param String message Test description
669
+ * @return Object An assertion result object
670
+ */
671
+ Tester.prototype.assertUrlMatch = Tester.prototype.assertUrlMatches = function assertUrlMatch(pattern, message) {
672
+ "use strict";
673
+ var currentUrl = this.casper.getCurrentUrl(),
674
+ patternType = utils.betterTypeOf(pattern),
675
+ result;
676
+ if (patternType === "regexp") {
677
+ result = pattern.test(currentUrl);
678
+ } else if (patternType === "string") {
679
+ result = currentUrl.indexOf(pattern) !== -1;
680
+ } else {
681
+ throw new CasperError("assertUrlMatch() only accepts strings or regexps");
682
+ }
683
+ return this.assert(result, message, {
684
+ type: "assertUrlMatch",
685
+ standard: "Current url matches the provided pattern",
686
+ values: {
687
+ currentUrl: currentUrl,
688
+ pattern: pattern.toString()
689
+ }
690
+ });
691
+ };
692
+
693
+ /**
694
+ * Asserts that a selector expression is currently visible.
695
+ *
696
+ * @param String expected selector expression
697
+ * @param String message Test description
698
+ * @return Object An assertion result object
699
+ */
700
+ Tester.prototype.assertVisible = function assertVisible(selector, message) {
701
+ "use strict";
702
+ return this.assert(this.casper.visible(selector), message, {
703
+ type: "assertVisible",
704
+ standard: "Selector is visible",
705
+ values: {
706
+ selector: selector
707
+ }
708
+ });
709
+ };
710
+
711
+ /**
712
+ * Prints out a colored bar onto the console.
713
+ *
714
+ */
715
+ Tester.prototype.bar = function bar(text, style) {
716
+ "use strict";
717
+ this.casper.echo(text, style, this.options.pad);
718
+ };
719
+
720
+ /**
721
+ * Retrieves the sum of all durations of the tests which were
722
+ * executed in the current suite
723
+ *
724
+ * @return Number duration of all tests executed until now (in the current suite)
725
+ */
726
+ Tester.prototype.calculateSuiteDuration = function calculateSuiteDuration() {
727
+ "use strict";
728
+ return this.testResults.passesTime.concat(this.testResults.failuresTime).reduce(function add(a, b) {
729
+ return a + b;
730
+ }, 0);
731
+ };
732
+
733
+ /**
734
+ * Render a colorized output. Basically a proxy method for
735
+ * Casper.Colorizer#colorize()
736
+ */
737
+ Tester.prototype.colorize = function colorize(message, style) {
738
+ "use strict";
739
+ return this.casper.getColorizer().colorize(message, style);
740
+ };
741
+
742
+ /**
743
+ * Writes a comment-style formatted message to stdout.
744
+ *
745
+ * @param String message
746
+ */
747
+ Tester.prototype.comment = function comment(message) {
748
+ "use strict";
749
+ this.casper.echo('# ' + message, 'COMMENT');
750
+ };
751
+
752
+ /**
753
+ * Configure casper callbacks for testing purpose.
754
+ *
755
+ */
756
+ Tester.prototype.configure = function configure() {
757
+ "use strict";
758
+ var tester = this;
759
+
760
+ // Do not hook casper if we're not testing
761
+ if (!phantom.casperTest) {
762
+ return;
763
+ }
764
+
765
+ // specific timeout callbacks
766
+ this.casper.options.onStepTimeout = function test_onStepTimeout(timeout, step) {
767
+ tester.fail(f("Step timeout occured at step %s (%dms)", step, timeout));
768
+ };
769
+
770
+ this.casper.options.onTimeout = function test_onTimeout(timeout) {
771
+ tester.fail(f("Timeout occured (%dms)", timeout));
772
+ };
773
+
774
+ this.casper.options.onWaitTimeout = function test_onWaitTimeout(timeout) {
775
+ tester.fail(f("Wait timeout occured (%dms)", timeout));
776
+ };
777
+ };
778
+
779
+ /**
780
+ * Declares the current test suite done.
781
+ *
782
+ * @param Number planned Number of planned tests
783
+ */
784
+ Tester.prototype.done = function done(planned) {
785
+ "use strict";
786
+ if (planned > 0 && planned !== this.executed) {
787
+ this.fail(f('%s: %d tests planned, %d tests executed',
788
+ this.currentTestFile, planned, this.executed));
789
+ }
790
+ this.emit('test.done');
791
+ this.running = false;
792
+ };
793
+
794
+ /**
795
+ * Writes an error-style formatted message to stdout.
796
+ *
797
+ * @param String message
798
+ */
799
+ Tester.prototype.error = function error(message) {
800
+ "use strict";
801
+ this.casper.echo(message, 'ERROR');
802
+ };
803
+
804
+ /**
805
+ * Executes a file, wraping and evaluating its code in an isolated
806
+ * environment where only the current `casper` instance is passed.
807
+ *
808
+ * @param String file Absolute path to some js/coffee file
809
+ */
810
+ Tester.prototype.exec = function exec(file) {
811
+ "use strict";
812
+ file = this.filter('exec.file', file) || file;
813
+ if (!fs.isFile(file) || !utils.isJsFile(file)) {
814
+ var e = new CasperError(f("Cannot exec %s: can only exec() files with .js or .coffee extensions", file));
815
+ e.fileName = file;
816
+ throw e;
817
+ }
818
+ this.currentTestFile = file;
819
+ phantom.injectJs(file);
820
+ };
821
+
822
+ /**
823
+ * Adds a failed test entry to the stack.
824
+ *
825
+ * @param String message
826
+ */
827
+ Tester.prototype.fail = function fail(message) {
828
+ "use strict";
829
+ return this.assert(false, message, {
830
+ type: "fail",
831
+ standard: "explicit call to fail()"
832
+ });
833
+ };
834
+
835
+ /**
836
+ * Recursively finds all test files contained in a given directory.
837
+ *
838
+ * @param String dir Path to some directory to scan
839
+ */
840
+ Tester.prototype.findTestFiles = function findTestFiles(dir) {
841
+ "use strict";
842
+ var self = this;
843
+ if (!fs.isDirectory(dir)) {
844
+ return [];
845
+ }
846
+ var entries = fs.list(dir).filter(function _filter(entry) {
847
+ return entry !== '.' && entry !== '..';
848
+ }).map(function _map(entry) {
849
+ return fs.absolute(fs.pathJoin(dir, entry));
850
+ });
851
+ entries.forEach(function _forEach(entry) {
852
+ if (fs.isDirectory(entry)) {
853
+ entries = entries.concat(self.findTestFiles(entry));
854
+ }
855
+ });
856
+ return entries.filter(function _filter(entry) {
857
+ return utils.isJsFile(fs.absolute(fs.pathJoin(dir, entry)));
858
+ }).sort();
859
+ };
860
+
861
+ /**
862
+ * Formats a message to highlight some parts of it.
863
+ *
864
+ * @param String message
865
+ * @param String style
866
+ */
867
+ Tester.prototype.formatMessage = function formatMessage(message, style) {
868
+ "use strict";
869
+ var parts = /^([a-z0-9_\.]+\(\))(.*)/i.exec(message);
870
+ if (!parts) {
871
+ return message;
872
+ }
873
+ return this.colorize(parts[1], 'PARAMETER') + this.colorize(parts[2], style);
874
+ };
875
+
876
+ /**
877
+ * Retrieves current failure data and all failed cases.
878
+ *
879
+ * @return Object casedata An object containg information about cases
880
+ * @return Number casedata.length The number of failed cases
881
+ * @return Array casedata.cases An array of all the failed case objects
882
+ */
883
+ Tester.prototype.getFailures = function getFailures() {
884
+ "use strict";
885
+ return {
886
+ length: this.testResults.failed,
887
+ cases: this.testResults.failures
888
+ };
889
+ };
890
+
891
+ /**
892
+ * Retrieves current passed data and all passed cases.
893
+ *
894
+ * @return Object casedata An object containg information about cases
895
+ * @return Number casedata.length The number of passed cases
896
+ * @return Array casedata.cases An array of all the passed case objects
897
+ */
898
+ Tester.prototype.getPasses = function getPasses() {
899
+ "use strict";
900
+ return {
901
+ length: this.testResults.passed,
902
+ cases: this.testResults.passes
903
+ };
904
+ };
905
+
906
+ /**
907
+ * Retrieves the array where all the durations of failed tests are stored
908
+ *
909
+ * @return Array durations of failed tests
910
+ */
911
+ Tester.prototype.getFailuresTime = function getFailuresTime() {
912
+ "use strict";
913
+ return this.testResults.failuresTime;
914
+ }
915
+
916
+ /**
917
+ * Retrieves the array where all the durations of passed tests are stored
918
+ *
919
+ * @return Array durations of passed tests
920
+ */
921
+ Tester.prototype.getPassesTime = function getPassesTime() {
922
+ "use strict";
923
+ return this.testResults.passesTime;
924
+ }
925
+
926
+
927
+ /**
928
+ * Writes an info-style formatted message to stdout.
929
+ *
930
+ * @param String message
931
+ */
932
+ Tester.prototype.info = function info(message) {
933
+ "use strict";
934
+ this.casper.echo(message, 'PARAMETER');
935
+ };
936
+
937
+ /**
938
+ * Adds a successful test entry to the stack.
939
+ *
940
+ * @param String message
941
+ */
942
+ Tester.prototype.pass = function pass(message) {
943
+ "use strict";
944
+ return this.assert(true, message, {
945
+ type: "pass",
946
+ standard: "explicit call to pass()"
947
+ });
948
+ };
949
+
950
+ /**
951
+ * Processes an assertion result by emitting the appropriate event and
952
+ * printing result onto the console.
953
+ *
954
+ * @param Object result An assertion result object
955
+ * @return Object The passed assertion result Object
956
+ */
957
+ Tester.prototype.processAssertionResult = function processAssertionResult(result) {
958
+ "use strict";
959
+ var eventName= 'success',
960
+ message = result.message || result.standard,
961
+ style = 'INFO',
962
+ status = this.options.passText;
963
+ if (!result.success) {
964
+ eventName = 'fail';
965
+ style = 'RED_BAR';
966
+ status = this.options.failText;
967
+ this.testResults.failed++;
968
+ } else {
969
+ this.testResults.passed++;
970
+ }
971
+ this.casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' '));
972
+ this.emit(eventName, result);
973
+ if (this.options.failFast && !result.success) {
974
+ throw this.SKIP_MESSAGE;
975
+ }
976
+ return result;
977
+ };
978
+
979
+ /**
980
+ * Renders a detailed report for each failed test.
981
+ *
982
+ * @param Array failures
983
+ */
984
+ Tester.prototype.renderFailureDetails = function renderFailureDetails(failures) {
985
+ "use strict";
986
+ if (failures.length === 0) {
987
+ return;
988
+ }
989
+ this.casper.echo(f("\nDetails for the %d failed test%s:\n", failures.length, failures.length > 1 ? "s" : ""), "PARAMETER");
990
+ failures.forEach(function _forEach(failure) {
991
+ var type, message, line;
992
+ type = failure.type || "unknown";
993
+ line = ~~failure.line;
994
+ message = failure.message;
995
+ this.casper.echo(f('In %s:%s', failure.file, line));
996
+ this.casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT");
997
+ }.bind(this));
998
+ };
999
+
1000
+ /**
1001
+ * Render tests results, an optionally exit phantomjs.
1002
+ *
1003
+ * @param Boolean exit
1004
+ */
1005
+ Tester.prototype.renderResults = function renderResults(exit, status, save) {
1006
+ "use strict";
1007
+ /*jshint maxstatements:20*/
1008
+ save = save || this.options.save;
1009
+ var total = this.testResults.passed + this.testResults.failed, statusText, style, result;
1010
+ var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0));
1011
+ if (total === 0) {
1012
+ statusText = this.options.warnText;
1013
+ style = 'WARN_BAR';
1014
+ result = f("%s Looks like you didn't run any test.", statusText);
1015
+ } else {
1016
+ if (this.testResults.failed > 0) {
1017
+ statusText = this.options.failText;
1018
+ style = 'RED_BAR';
1019
+ } else {
1020
+ statusText = this.options.passText;
1021
+ style = 'GREEN_BAR';
1022
+ }
1023
+ result = f('%s %s tests executed in %ss, %d passed, %d failed.',
1024
+ statusText, total, utils.ms2seconds(this.calculateSuiteDuration()),
1025
+ this.testResults.passed, this.testResults.failed);
1026
+ }
1027
+ this.casper.echo(result, style, this.options.pad);
1028
+ if (this.testResults.failed > 0) {
1029
+ this.renderFailureDetails(this.testResults.failures);
1030
+ }
1031
+ if (save) {
1032
+ this.saveResults(save);
1033
+ }
1034
+ if (exit === true) {
1035
+ this.casper.exit(exitStatus);
1036
+ }
1037
+ };
1038
+
1039
+ /**
1040
+ * Runs al suites contained in the paths passed as arguments.
1041
+ *
1042
+ */
1043
+ Tester.prototype.runSuites = function runSuites() {
1044
+ "use strict";
1045
+ var testFiles = [], self = this;
1046
+ if (arguments.length === 0) {
1047
+ throw new CasperError("runSuites() needs at least one path argument");
1048
+ }
1049
+ this.loadIncludes.includes.forEach(function _forEachInclude(include) {
1050
+ phantom.injectJs(include);
1051
+ });
1052
+
1053
+ this.loadIncludes.pre.forEach(function _forEachPreTest(preTestFile) {
1054
+ testFiles = testFiles.concat(preTestFile);
1055
+ });
1056
+
1057
+ Array.prototype.forEach.call(arguments, function _forEachArgument(path) {
1058
+ if (!fs.exists(path)) {
1059
+ self.bar(f("Path %s doesn't exist", path), "RED_BAR");
1060
+ }
1061
+ if (fs.isDirectory(path)) {
1062
+ testFiles = testFiles.concat(self.findTestFiles(path));
1063
+ } else if (fs.isFile(path)) {
1064
+ testFiles.push(path);
1065
+ }
1066
+ });
1067
+
1068
+ this.loadIncludes.post.forEach(function _forEachPostTest(postTestFile) {
1069
+ testFiles = testFiles.concat(postTestFile);
1070
+ });
1071
+
1072
+ if (testFiles.length === 0) {
1073
+ this.bar(f("No test file found in %s, aborting.", Array.prototype.slice.call(arguments)), "RED_BAR");
1074
+ this.casper.exit(1);
1075
+ }
1076
+ self.currentSuiteNum = 0;
1077
+ self.currentTestStartTime = new Date();
1078
+ self.lastAssertTime = 0;
1079
+ var interval = setInterval(function _check(self) {
1080
+ if (self.running) {
1081
+ return;
1082
+ }
1083
+ if (self.currentSuiteNum === testFiles.length || self.aborted) {
1084
+ self.emit('tests.complete');
1085
+ clearInterval(interval);
1086
+ self.exporter.setSuiteDuration(self.calculateSuiteDuration());
1087
+ self.aborted = false;
1088
+ } else {
1089
+ self.runTest(testFiles[self.currentSuiteNum]);
1090
+ self.exporter.setSuiteDuration(self.calculateSuiteDuration());
1091
+ self.currentSuiteNum++;
1092
+ self.passesTime = [];
1093
+ self.failuresTime = [];
1094
+ }
1095
+ }, 100, this);
1096
+ };
1097
+
1098
+ /**
1099
+ * Runs a test file
1100
+ *
1101
+ */
1102
+ Tester.prototype.runTest = function runTest(testFile) {
1103
+ "use strict";
1104
+ this.bar(f('Test file: %s', testFile), 'INFO_BAR');
1105
+ this.running = true; // this.running is set back to false with done()
1106
+ this.executed = 0;
1107
+ this.exec(testFile);
1108
+ };
1109
+
1110
+ /**
1111
+ * Saves results to file.
1112
+ *
1113
+ * @param String filename Target file path.
1114
+ */
1115
+ Tester.prototype.saveResults = function saveResults(filepath) {
1116
+ "use strict";
1117
+ // FIXME: looks like phantomjs has a pb with fs.isWritable https://groups.google.com/forum/#!topic/casperjs/hcUdwgGZOrU
1118
+ // if (!fs.isWritable(filepath)) {
1119
+ // throw new CasperError(f('Path %s is not writable.', filepath));
1120
+ // }
1121
+ try {
1122
+ fs.write(filepath, this.exporter.getXML(), 'w');
1123
+ this.casper.echo(f('Result log stored in %s', filepath), 'INFO', 80);
1124
+ } catch (e) {
1125
+ this.casper.echo(f('Unable to write results to %s: %s', filepath, e), 'ERROR', 80);
1126
+ }
1127
+ };
1128
+
1129
+ /**
1130
+ * Tests equality between the two passed arguments.
1131
+ *
1132
+ * @param Mixed v1
1133
+ * @param Mixed v2
1134
+ * @param Boolean
1135
+ */
1136
+ Tester.prototype.testEquals = Tester.prototype.testEqual = function testEquals(v1, v2) {
1137
+ "use strict";
1138
+ return utils.equals(v1, v2);
1139
+ };
1140
+
1141
+ /**
1142
+ * Processes an error caught while running tests contained in a given test
1143
+ * file.
1144
+ *
1145
+ * @param Error|String error The error
1146
+ * @param String file Test file where the error occurred
1147
+ * @param Number line Line number (optional)
1148
+ */
1149
+ Tester.prototype.uncaughtError = function uncaughtError(error, file, line) {
1150
+ "use strict";
1151
+ return this.processAssertionResult({
1152
+ success: false,
1153
+ type: "uncaughtError",
1154
+ file: file,
1155
+ line: ~~line || "unknown",
1156
+ message: utils.isObject(error) ? error.message : error,
1157
+ values: {
1158
+ error: error
1159
+ }
1160
+ });
1161
+ };