casperjs 1.0.0.RC1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/CHANGELOG.md +289 -1
  2. data/CONTRIBUTING.md +93 -0
  3. data/CONTRIBUTORS.md +53 -0
  4. data/README.md +29 -1
  5. data/batchbin/casperjs.bat +5 -0
  6. data/bin/bootstrap.js +164 -114
  7. data/bin/casperjs +84 -0
  8. data/bin/usage.txt +2 -1
  9. data/casperjs.gemspec +7 -4
  10. data/modules/casper.js +661 -239
  11. data/modules/clientutils.js +240 -54
  12. data/modules/colorizer.js +2 -1
  13. data/modules/events.js +13 -1
  14. data/modules/http.js +70 -0
  15. data/modules/mouse.js +1 -2
  16. data/modules/pagestack.js +166 -0
  17. data/modules/tester.js +1013 -659
  18. data/modules/utils.js +519 -367
  19. data/modules/vendors/coffee-script.js +2 -2
  20. data/modules/xunit.js +56 -24
  21. data/package.json +2 -2
  22. data/rpm/casperjs.spec +203 -0
  23. data/rpm/mkrpm.sh +25 -0
  24. data/rubybin/casperjs +9 -4
  25. data/samples/bbcshots.coffee +11 -10
  26. data/samples/bbcshots.js +16 -15
  27. data/samples/cliplay.js +3 -0
  28. data/samples/customevents.js +3 -0
  29. data/samples/customlogging.coffee +17 -19
  30. data/samples/customlogging.js +26 -27
  31. data/samples/download.js +4 -1
  32. data/samples/dynamic.js +3 -0
  33. data/samples/each.js +3 -0
  34. data/samples/events.js +4 -2
  35. data/samples/extends.js +4 -1
  36. data/samples/googlelinks.coffee +4 -1
  37. data/samples/googlelinks.js +10 -3
  38. data/samples/googlematch.coffee +1 -1
  39. data/samples/googlematch.js +5 -2
  40. data/samples/googlepagination.js +4 -1
  41. data/samples/googletesting.js +3 -0
  42. data/samples/logcolor.js +3 -0
  43. data/samples/metaextract.js +3 -0
  44. data/samples/multirun.js +3 -0
  45. data/samples/screenshot.js +4 -1
  46. data/samples/statushandlers.js +4 -1
  47. data/samples/steptimeout.js +3 -0
  48. data/samples/timeout.js +4 -1
  49. data/samples/translate.coffee +23 -0
  50. data/samples/translate.js +30 -0
  51. data/tests/commands/mytest.js +14 -0
  52. data/tests/commands/script.js +3 -0
  53. data/tests/run.js +82 -37
  54. data/tests/sample_modules/config.json +1 -0
  55. data/tests/sample_modules/csmodule.coffee +1 -0
  56. data/tests/sample_modules/jsmodule.js +1 -0
  57. data/tests/selftest.js +57 -0
  58. data/tests/site/dummy.js +1 -0
  59. data/tests/site/field-array.html +14 -0
  60. data/tests/site/frame1.html +10 -0
  61. data/tests/site/frame2.html +11 -0
  62. data/tests/site/frame3.html +11 -0
  63. data/tests/site/frames.html +12 -0
  64. data/tests/site/global.html +6 -1
  65. data/tests/site/includes/include1.js +6 -0
  66. data/tests/site/includes/include2.js +6 -0
  67. data/tests/site/index.html +3 -0
  68. data/tests/site/popup.html +19 -0
  69. data/tests/site/resources.html +7 -8
  70. data/tests/site/urls.html +14 -0
  71. data/tests/suites/casper/agent.js +3 -1
  72. data/tests/suites/casper/alert.js +14 -0
  73. data/tests/suites/casper/auth.js +24 -0
  74. data/tests/suites/casper/capture.js +12 -12
  75. data/tests/suites/casper/click.js +11 -1
  76. data/tests/suites/casper/confirm.js +24 -16
  77. data/tests/suites/casper/debug.js +10 -0
  78. data/tests/suites/casper/elementattribute.js +3 -1
  79. data/tests/suites/casper/encode.js +6 -2
  80. data/tests/suites/casper/evaluate.js +78 -18
  81. data/tests/suites/casper/events.js +3 -1
  82. data/tests/suites/casper/exists.js +3 -1
  83. data/tests/suites/casper/fetchtext.js +3 -1
  84. data/tests/suites/casper/flow.coffee +1 -1
  85. data/tests/suites/casper/formfill.js +39 -4
  86. data/tests/suites/casper/frames.js +43 -0
  87. data/tests/suites/casper/global.js +9 -3
  88. data/tests/suites/casper/headers.js +41 -0
  89. data/tests/suites/casper/history.js +3 -1
  90. data/tests/suites/casper/hooks.js +3 -1
  91. data/tests/suites/casper/keys.js +15 -0
  92. data/tests/suites/casper/location.js +21 -0
  93. data/tests/suites/casper/logging.js +3 -1
  94. data/tests/suites/casper/mouseevents.js +3 -1
  95. data/tests/suites/casper/onerror.js +3 -1
  96. data/tests/suites/casper/open.js +63 -1
  97. data/tests/suites/casper/popup.js +86 -0
  98. data/tests/suites/casper/prompt.js +11 -15
  99. data/tests/suites/casper/request.js +36 -0
  100. data/tests/suites/casper/resources.coffee +5 -5
  101. data/tests/suites/casper/scripts.js +32 -0
  102. data/tests/suites/casper/start.js +3 -1
  103. data/tests/suites/casper/steps.js +4 -2
  104. data/tests/suites/casper/urls.js +21 -0
  105. data/tests/suites/casper/viewport.js +3 -1
  106. data/tests/suites/casper/visible.js +3 -1
  107. data/tests/suites/casper/wait.js +22 -12
  108. data/tests/suites/casper/xpath.js +3 -1
  109. data/tests/suites/cli.js +3 -1
  110. data/tests/suites/clientutils.js +57 -3
  111. data/tests/suites/coffee.coffee +1 -1
  112. data/tests/suites/fs.js +3 -1
  113. data/tests/suites/http_status.js +19 -3
  114. data/tests/suites/popup.js +33 -0
  115. data/tests/suites/require.js +31 -0
  116. data/tests/suites/tester.js +134 -29
  117. data/tests/suites/utils.js +108 -8
  118. data/tests/suites/xunit.js +37 -6
  119. metadata +49 -15
  120. data/modules/injector.js +0 -93
  121. data/tests/suites/injector.js +0 -64
@@ -19,6 +19,8 @@
19
19
  // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20
20
  // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ /*global CasperError*/
23
+
22
24
  var isArray = Array.isArray;
23
25
 
24
26
  function EventEmitter() {
@@ -51,7 +53,6 @@ EventEmitter.prototype.emit = function() {
51
53
  } else {
52
54
  throw new CasperError("Uncaught, unspecified 'error' event.");
53
55
  }
54
- return false;
55
56
  }
56
57
  }
57
58
 
@@ -231,6 +232,17 @@ EventEmitter.prototype.filter = function() {
231
232
  return filter.apply(this, Array.prototype.splice.call(arguments, 1));
232
233
  };
233
234
 
235
+ EventEmitter.prototype.removeAllFilters = function(type) {
236
+ if (arguments.length === 0) {
237
+ this._filters = {};
238
+ return this;
239
+ }
240
+ if (type && this._filters && this._filters[type]) {
241
+ this._filters[type] = null;
242
+ }
243
+ return this;
244
+ };
245
+
234
246
  EventEmitter.prototype.setFilter = function(type, filterFn) {
235
247
  if (!this._filters) {
236
248
  this._filters = {};
@@ -0,0 +1,70 @@
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
+ var utils = require('utils');
32
+
33
+ /*
34
+ * Building an Array subclass
35
+ */
36
+ function responseHeaders(){}
37
+ responseHeaders.prototype = [];
38
+
39
+ /**
40
+ * Retrieves a given header based on its name
41
+ *
42
+ * @param String name A case-insensitive response header name
43
+ * @return mixed A header string or `null` if not found
44
+ */
45
+ responseHeaders.prototype.get = function get(name){
46
+ "use strict";
47
+ var headerValue = null;
48
+ name = name.toLowerCase();
49
+ this.some(function(header){
50
+ if (header.name.toLowerCase() === name){
51
+ headerValue = header.value;
52
+ return true;
53
+ }
54
+ });
55
+ return headerValue;
56
+ };
57
+
58
+ /**
59
+ * Augment the response with proper prototypes
60
+ *
61
+ * @param mixed response Phantom response or undefined (generally with local files)
62
+ */
63
+ exports.augmentResponse = function(response) {
64
+ "use strict";
65
+ /*jshint proto:true*/
66
+ if (!utils.isHTTPResource(response)) {
67
+ return;
68
+ }
69
+ response.headers.__proto__ = responseHeaders.prototype;
70
+ };
@@ -72,8 +72,7 @@ var Mouse = function Mouse(casper) {
72
72
  throw new CasperError('Mouse.processEvent(): Too few arguments');
73
73
  case 1:
74
74
  // selector
75
- var selector = args[0];
76
- casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(selector)));
75
+ casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(args[0])));
77
76
  break;
78
77
  case 2:
79
78
  // coordinates
@@ -0,0 +1,166 @@
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 console exports phantom require*/
32
+
33
+ var utils = require('utils');
34
+ var f = utils.format;
35
+
36
+ function create() {
37
+ "use strict";
38
+ return new Stack();
39
+ }
40
+ exports.create = create;
41
+
42
+ /**
43
+ * Popups container. Implements Array prototype.
44
+ *
45
+ */
46
+ var Stack = function Stack(){};
47
+ exports.Stack = Stack;
48
+
49
+ Stack.prototype = [];
50
+
51
+ /**
52
+ * Cleans the stack from closed popup.
53
+ *
54
+ * @param WebPage closed Closed popup page instance
55
+ * @return Number New stack length
56
+ */
57
+ Stack.prototype.clean = function clean(closed) {
58
+ "use strict";
59
+ var closedIndex = -1;
60
+ this.forEach(function(popup, index) {
61
+ if (closed === popup) {
62
+ closedIndex = index;
63
+ }
64
+ });
65
+ if (closedIndex > -1) {
66
+ this.splice(closedIndex, 1);
67
+ }
68
+ return this.length;
69
+ };
70
+
71
+ /**
72
+ * Finds a popup matching the provided information. Information can be:
73
+ *
74
+ * - RegExp: matching page url
75
+ * - String: strict page url value
76
+ * - WebPage: a direct WebPage instance
77
+ *
78
+ * @param Mixed popupInfo
79
+ * @return WebPage
80
+ */
81
+ Stack.prototype.find = function find(popupInfo) {
82
+ "use strict";
83
+ var popup, type = utils.betterTypeOf(popupInfo);
84
+ switch (type) {
85
+ case "regexp":
86
+ popup = this.findByRegExp(popupInfo);
87
+ break;
88
+ case "string":
89
+ popup = this.findByURL(popupInfo);
90
+ break;
91
+ case "qtruntimeobject": // WebPage
92
+ popup = popupInfo;
93
+ if (!utils.isWebPage(popup) || !this.some(function(popupPage) {
94
+ if (popupInfo.id && popupPage.id) {
95
+ return popupPage.id === popup.id;
96
+ }
97
+ return popupPage.url === popup.url;
98
+ })) {
99
+ throw new CasperError("Invalid or missing popup.");
100
+ }
101
+ break;
102
+ default:
103
+ throw new CasperError(f("Invalid popupInfo type: %s.", type));
104
+ }
105
+ return popup;
106
+ };
107
+
108
+ /**
109
+ * Finds the first popup which url matches a given RegExp.
110
+ *
111
+ * @param RegExp regexp
112
+ * @return WebPage
113
+ */
114
+ Stack.prototype.findByRegExp = function findByRegExp(regexp) {
115
+ "use strict";
116
+ var popup = this.filter(function(popupPage) {
117
+ return regexp.test(popupPage.url);
118
+ })[0];
119
+ if (!popup) {
120
+ throw new CasperError(f("Couldn't find popup with url matching pattern %s", regexp));
121
+ }
122
+ return popup;
123
+ };
124
+
125
+ /**
126
+ * Finds the first popup matching a given url.
127
+ *
128
+ * @param String url The child WebPage url
129
+ * @return WebPage
130
+ */
131
+ Stack.prototype.findByURL = function findByURL(string) {
132
+ "use strict";
133
+ var popup = this.filter(function(popupPage) {
134
+ return popupPage.url.indexOf(string) !== -1;
135
+ })[0];
136
+ if (!popup) {
137
+ throw new CasperError(f("Couldn't find popup with url containing '%s'", string));
138
+ }
139
+ return popup;
140
+ };
141
+
142
+ /**
143
+ * Returns a human readable list of current active popup urls.
144
+ *
145
+ * @return Array Mapped stack.
146
+ */
147
+ Stack.prototype.list = function list() {
148
+ "use strict";
149
+ return this.map(function(popup) {
150
+ try {
151
+ return popup.url;
152
+ } catch (e) {
153
+ return '<deleted>';
154
+ }
155
+ });
156
+ };
157
+
158
+ /**
159
+ * String representation of current instance.
160
+ *
161
+ * @return String
162
+ */
163
+ Stack.prototype.toString = function toString() {
164
+ "use strict";
165
+ return f("[Object Stack], having %d popup(s)" % this.length);
166
+ };
@@ -28,7 +28,7 @@
28
28
  *
29
29
  */
30
30
 
31
- /*global CasperError exports phantom require*/
31
+ /*global CasperError exports phantom require __utils__*/
32
32
 
33
33
  var fs = require('fs');
34
34
  var events = require('events');
@@ -48,20 +48,36 @@ exports.create = function create(casper, options) {
48
48
  */
49
49
  var Tester = function Tester(casper, options) {
50
50
  "use strict";
51
+ /*jshint maxstatements:30*/
51
52
 
52
53
  if (!utils.isCasperObject(casper)) {
53
54
  throw new CasperError("Tester needs a Casper instance");
54
55
  }
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;
56
65
  this.currentTestFile = null;
66
+ this.currentSuiteNum = 0;
57
67
  this.exporter = require('xunit').create();
58
- this.includes = [];
68
+ this.loadIncludes = {
69
+ includes: [],
70
+ pre: [],
71
+ post: []
72
+ };
59
73
  this.running = false;
60
74
  this.suites = [];
61
75
  this.options = utils.mergeObjects({
76
+ failFast: false, // terminates a suite as soon as a test fails?
62
77
  failText: "FAIL", // text to use for a successful test
63
78
  passText: "PASS", // text to use for a failed test
64
- pad: 80 // maximum number of chars for a result line
79
+ pad: 80 , // maximum number of chars for a result line
80
+ warnText: "WARN" // text to use for a dubious test
65
81
  }, options);
66
82
 
67
83
  // properties
@@ -69,739 +85,1077 @@ var Tester = function Tester(casper, options) {
69
85
  passed: 0,
70
86
  failed: 0,
71
87
  passes: [],
72
- failures: []
88
+ failures: [],
89
+ passesTime: [],
90
+ failuresTime: []
73
91
  };
74
92
 
75
- // events
76
- casper.on('error', function(msg, backtrace) {
77
- var line = 0;
78
- try {
79
- line = backtrace[0].line;
80
- } catch (e) {}
81
- this.test.uncaughtError(msg, this.test.currentTestFile, line);
82
- this.test.done();
83
- });
93
+ // measuring test duration
94
+ this.currentTestStartTime = new Date();
95
+ this.lastAssertTime = 0;
84
96
 
85
- casper.on('step.error', function onStepError(e) {
86
- this.test.uncaughtError(e, this.test.currentTestFile);
87
- this.test.done();
88
- });
97
+ this.configure();
89
98
 
90
99
  this.on('success', function onSuccess(success) {
91
100
  this.testResults.passes.push(success);
92
- this.exporter.addSuccess(fs.absolute(success.file), success.message || success.standard);
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;
93
105
  });
94
106
 
95
107
  this.on('fail', function onFail(failure) {
96
108
  // export
109
+ var timeElapsed = new Date() - this.currentTestStartTime;
110
+ this.testResults.failuresTime.push(timeElapsed - this.lastAssertTime);
97
111
  this.exporter.addFailure(
98
112
  fs.absolute(failure.file),
99
113
  failure.message || failure.standard,
100
114
  failure.standard || "test failed",
101
- failure.type || "unknown"
115
+ failure.type || "unknown",
116
+ (timeElapsed - this.lastAssertTime)
102
117
  );
118
+ this.lastAssertTime = timeElapsed;
103
119
  this.testResults.failures.push(failure);
120
+
104
121
  // special printing
105
122
  if (failure.type) {
106
123
  this.comment(' type: ' + failure.type);
107
124
  }
108
125
  if (failure.values && Object.keys(failure.values).length > 0) {
109
126
  for (var name in failure.values) {
110
- this.comment(' ' + name + ': ' + utils.serialize(failure.values[name]));
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);
111
139
  }
112
140
  }
113
141
  });
114
142
 
115
- // methods
116
- /**
117
- * Asserts that a condition strictly resolves to true. Also returns an
118
- * "assertion object" containing useful informations about the test case
119
- * results.
120
- *
121
- * This method is also used as the base one used for all other `assert*`
122
- * family methods; supplementary informations are then passed using the
123
- * `context` argument.
124
- *
125
- * @param Boolean subject The condition to test
126
- * @param String message Test description
127
- * @param Object|null context Assertion context object (Optional)
128
- * @return Object An assertion result object
129
- */
130
- this.assert = this.assertTrue = function assert(subject, message, context) {
131
- return this.processAssertionResult(utils.mergeObjects({
132
- success: subject === true,
133
- type: "assert",
134
- standard: "Subject is strictly true",
135
- message: message,
136
- file: this.currentTestFile,
137
- values: {
138
- subject: utils.getPropertyPath(context, 'values.subject') || subject
139
- }
140
- }, context || {}));
141
- };
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
+ });
142
162
 
143
- /**
144
- * Asserts that two values are strictly equals.
145
- *
146
- * @param Mixed subject The value to test
147
- * @param Mixed expected The expected value
148
- * @param String message Test description (Optional)
149
- * @return Object An assertion result object
150
- */
151
- this.assertEquals = this.assertEqual = function assertEquals(subject, expected, message) {
152
- return this.assert(this.testEquals(subject, expected), message, {
153
- type: "assertEquals",
154
- standard: "Subject equals the expected value",
155
- values: {
156
- subject: subject,
157
- expected: expected
158
- }
159
- });
160
- };
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
+ };
161
170
 
162
- /**
163
- * Asserts that two values are strictly not equals.
164
- *
165
- * @param Mixed subject The value to test
166
- * @param Mixed expected The unwanted value
167
- * @param String|null message Test description (Optional)
168
- * @return Object An assertion result object
169
- */
170
- this.assertNotEquals = function assertNotEquals(subject, shouldnt, message) {
171
- return this.assert(!this.testEquals(subject, shouldnt), message, {
172
- type: "assertNotEquals",
173
- standard: "Subject doesn't equal what it shouldn't be",
174
- values: {
175
- subject: subject,
176
- shouldnt: shouldnt
177
- }
178
- });
179
- };
171
+ // Tester class is an EventEmitter
172
+ utils.inherits(Tester, events.EventEmitter);
173
+ exports.Tester = Tester;
180
174
 
181
- /**
182
- * Asserts that a code evaluation in remote DOM resolves to true.
183
- *
184
- * @param Function fn A function to be evaluated in remote DOM
185
- * @param String message Test description
186
- * @param Object params Object containing the parameters to inject into the function (optional)
187
- * @return Object An assertion result object
188
- */
189
- this.assertEval = this.assertEvaluate = function assertEval(fn, message, params) {
190
- return this.assert(casper.evaluate(fn, params), message, {
191
- type: "assertEval",
192
- standard: "Evaluated function returns true",
193
- values: {
194
- fn: fn,
195
- params: params
196
- }
197
- });
198
- };
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
+ };
199
203
 
200
- /**
201
- * Asserts that the result of a code evaluation in remote DOM equals
202
- * an expected value.
203
- *
204
- * @param Function fn The function to be evaluated in remote DOM
205
- * @param Boolean expected The expected value
206
- * @param String|null message Test description
207
- * @param Object|null params Object containing the parameters to inject into the function (optional)
208
- * @return Object An assertion result object
209
- */
210
- this.assertEvalEquals = this.assertEvalEqual = function assertEvalEquals(fn, expected, message, params) {
211
- var subject = casper.evaluate(fn, params);
212
- return this.assert(this.testEquals(subject, expected), message, {
213
- type: "assertEvalEquals",
214
- standard: "Evaluated function returns the expected value",
215
- values: {
216
- fn: fn,
217
- params: params,
218
- subject: subject,
219
- expected: expected
220
- }
221
- });
222
- };
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
223
 
224
- /**
225
- * Asserts that an element matching the provided selector expression exists in
226
- * remote DOM.
227
- *
228
- * @param String selector Selector expression
229
- * @param String message Test description
230
- * @return Object An assertion result object
231
- */
232
- this.assertExists = this.assertExist = this.assertSelectorExists = this.assertSelectorExist = function assertExists(selector, message) {
233
- return this.assert(casper.exists(selector), message, {
234
- type: "assertExists",
235
- standard: f("Found an element matching %s", this.colorize(selector, 'COMMENT')),
236
- values: {
237
- selector: selector
238
- }
239
- });
240
- };
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
+ };
241
243
 
242
- /**
243
- * Asserts that an element matching the provided selector expression does not
244
- * exists in remote DOM.
245
- *
246
- * @param String selector Selector expression
247
- * @param String message Test description
248
- * @return Object An assertion result object
249
- */
250
- this.assertDoesntExist = this.assertNotExists = function assertDoesntExist(selector, message) {
251
- return this.assert(!casper.exists(selector), message, {
252
- type: "assertDoesntExist",
253
- standard: f("No element matching selector %s is found", this.colorize(selector, 'COMMENT')),
254
- values: {
255
- selector: selector
256
- }
257
- });
258
- };
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
+ };
259
263
 
260
- /**
261
- * Asserts that current HTTP status is the one passed as argument.
262
- *
263
- * @param Number status HTTP status code
264
- * @param String message Test description
265
- * @return Object An assertion result object
266
- */
267
- this.assertHttpStatus = function assertHttpStatus(status, message) {
268
- var currentHTTPStatus = casper.currentHTTPStatus;
269
- return this.assert(this.testEquals(casper.currentHTTPStatus, status), message, {
270
- type: "assertHttpStatus",
271
- standard: f("HTTP status code is %s", this.colorize(status, 'COMMENT')),
272
- values: {
273
- current: currentHTTPStatus,
274
- expected: status
275
- }
276
- });
277
- };
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
+ };
278
288
 
279
- /**
280
- * Asserts that a provided string matches a provided RegExp pattern.
281
- *
282
- * @param String subject The string to test
283
- * @param RegExp pattern A RegExp object instance
284
- * @param String message Test description
285
- * @return Object An assertion result object
286
- */
287
- this.assertMatch = this.assertMatches = function assertMatch(subject, pattern, message) {
288
- return this.assert(pattern.test(subject), message, {
289
- type: "assertMatch",
290
- standard: "Subject matches the provided pattern",
291
- values: {
292
- subject: subject,
293
- pattern: pattern.toString()
294
- }
295
- });
296
- };
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
+ };
297
312
 
298
- /**
299
- * Asserts a condition resolves to false.
300
- *
301
- * @param Boolean condition The condition to test
302
- * @param String message Test description
303
- * @return Object An assertion result object
304
- */
305
- this.assertNot = function assertNot(condition, message) {
306
- return this.assert(!condition, message, {
307
- type: "assertNot",
308
- standard: "Subject is falsy",
309
- values: {
310
- condition: condition
311
- }
312
- });
313
- };
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
+ };
314
331
 
315
- /**
316
- * Asserts that the provided function called with the given parameters
317
- * will raise an exception.
318
- *
319
- * @param Function fn The function to test
320
- * @param Array args The arguments to pass to the function
321
- * @param String message Test description
322
- * @return Object An assertion result object
323
- */
324
- this.assertRaises = this.assertRaise = this.assertThrows = function assertRaises(fn, args, message) {
325
- var context = {
326
- type: "assertRaises",
327
- standard: "Function raises an error"
328
- };
329
- try {
330
- fn.apply(null, args);
331
- this.assert(false, message, context);
332
- } catch (error) {
333
- this.assert(true, message, utils.mergeObjects(context, {
334
- values: {
335
- error: error
336
- }
337
- }));
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
338
347
  }
339
- };
348
+ });
349
+ };
340
350
 
341
- /**
342
- * Asserts that the current page has a resource that matches the provided test
343
- *
344
- * @param Function/String test A test function that is called with every response
345
- * @param String message Test description
346
- * @return Object An assertion result object
347
- */
348
- this.assertResourceExists = this.assertResourceExist = function assertResourceExists(test, message) {
349
- return this.assert(casper.resourceExists(test), message, {
350
- type: "assertResourceExists",
351
- standard: "Expected resource has been found",
352
- values: {
353
- test: test
354
- }
355
- });
356
- };
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
+ };
357
370
 
358
- /**
359
- * Asserts that given text exits in the document body.
360
- *
361
- * @param String text Text to be found
362
- * @param String message Test description
363
- * @return Object An assertion result object
364
- */
365
- this.assertTextExists = this.assertTextExist = function assertTextExists(text, message) {
366
- var textFound = (casper.evaluate(function _evaluate() {
367
- return document.body.textContent || document.body.innerText;
368
- }).indexOf(text) !== -1);
369
- return this.assert(textFound, message, {
370
- type: "assertTextExists",
371
- standard: "Found expected text within the document body",
372
- values: {
373
- text: text
374
- }
375
- });
376
- };
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
+ };
377
393
 
378
- /**
379
- * Asserts that title of the remote page equals to the expected one.
380
- *
381
- * @param String expected The expected title string
382
- * @param String message Test description
383
- * @return Object An assertion result object
384
- */
385
- this.assertTitle = function assertTitle(expected, message) {
386
- var currentTitle = casper.getTitle();
387
- return this.assert(this.testEquals(currentTitle, expected), message, {
388
- type: "assertTitle",
389
- standard: f('Page title is "%s"', this.colorize(expected, 'COMMENT')),
390
- values: {
391
- subject: currentTitle,
392
- expected: expected
393
- }
394
- });
395
- };
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
+ };
396
411
 
397
- /**
398
- * Asserts that title of the remote page matched the provided pattern.
399
- *
400
- * @param RegExp pattern The pattern to test the title against
401
- * @param String message Test description
402
- * @return Object An assertion result object
403
- */
404
- this.assertTitleMatch = this.assertTitleMatches = function assertTitleMatch(pattern, message) {
405
- var currentTitle = casper.getTitle();
406
- return this.assert(pattern.test(currentTitle), message, {
407
- type: "assertTitle",
408
- details: "Page title does not match the provided pattern",
409
- values: {
410
- subject: currentTitle,
411
- pattern: pattern.toString()
412
- }
413
- });
414
- };
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
+ };
415
429
 
416
- /**
417
- * Asserts that the provided subject is of the given type.
418
- *
419
- * @param mixed subject The value to test
420
- * @param String type The javascript type name
421
- * @param String message Test description
422
- * @return Object An assertion result object
423
- */
424
- this.assertType = function assertType(subject, type, message) {
425
- var actual = utils.betterTypeOf(subject);
426
- return this.assert(this.testEquals(actual, type), message, {
427
- type: "assertType",
428
- standard: f('Subject type is "%s"', this.colorize(type, 'COMMENT')),
429
- values: {
430
- subject: subject,
431
- type: type,
432
- actual: actual
433
- }
434
- });
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"
435
444
  };
436
-
437
- /**
438
- * Asserts that a the current page url matches the provided RegExp
439
- * pattern.
440
- *
441
- * @param RegExp pattern A RegExp object instance
442
- * @param String message Test description
443
- * @return Object An assertion result object
444
- */
445
- this.assertUrlMatch = this.assertUrlMatches = function assertUrlMatch(pattern, message) {
446
- var currentUrl = casper.getCurrentUrl();
447
- return this.assert(pattern.test(currentUrl), message, {
448
- type: "assertUrlMatch",
449
- standard: "Current url matches the provided pattern",
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
450
  values: {
451
- currentUrl: currentUrl,
452
- pattern: pattern.toString()
451
+ error: error
453
452
  }
454
- });
455
- };
453
+ }));
454
+ }
455
+ };
456
456
 
457
- /**
458
- * Prints out a colored bar onto the console.
459
- *
460
- */
461
- this.bar = function bar(text, style) {
462
- casper.echo(text, style, this.options.pad);
463
- };
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
+ };
464
474
 
465
- /**
466
- * Render a colorized output. Basically a proxy method for
467
- * Casper.Colorizer#colorize()
468
- */
469
- this.colorize = function colorize(message, style) {
470
- return casper.getColorizer().colorize(message, style);
471
- };
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
+ };
472
495
 
473
- /**
474
- * Writes a comment-style formatted message to stdout.
475
- *
476
- * @param String message
477
- */
478
- this.comment = function comment(message) {
479
- casper.echo('# ' + message, 'COMMENT');
480
- };
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
+ };
481
516
 
482
- /**
483
- * Declares the current test suite done.
484
- *
485
- */
486
- this.done = function done() {
487
- this.running = false;
488
- };
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
+ };
489
535
 
490
- /**
491
- * Writes an error-style formatted message to stdout.
492
- *
493
- * @param String message
494
- */
495
- this.error = function error(message) {
496
- casper.echo(message, 'ERROR');
497
- };
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
+ };
498
554
 
499
- /**
500
- * Executes a file, wraping and evaluating its code in an isolated
501
- * environment where only the current `casper` instance is passed.
502
- *
503
- * @param String file Absolute path to some js/coffee file
504
- */
505
- this.exec = function exec(file) {
506
- file = this.filter('exec.file', file) || file;
507
- if (!fs.isFile(file) || !utils.isJsFile(file)) {
508
- var e = new CasperError(f("Cannot exec %s: can only exec() files with .js or .coffee extensions", file));
509
- e.fileName = file;
510
- throw e;
511
- }
512
- this.currentTestFile = file;
513
- phantom.injectJs(file);
514
- };
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
+ };
515
575
 
516
- /**
517
- * Adds a failed test entry to the stack.
518
- *
519
- * @param String message
520
- */
521
- this.fail = function fail(message) {
522
- return this.assert(false, message, {
523
- type: "fail",
524
- standard: "explicit call to fail()"
525
- });
526
- };
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
+ };
527
596
 
528
- /**
529
- * Recursively finds all test files contained in a given directory.
530
- *
531
- * @param String dir Path to some directory to scan
532
- */
533
- this.findTestFiles = function findTestFiles(dir) {
534
- var self = this;
535
- if (!fs.isDirectory(dir)) {
536
- return [];
537
- }
538
- var entries = fs.list(dir).filter(function _filter(entry) {
539
- return entry !== '.' && entry !== '..';
540
- }).map(function _map(entry) {
541
- return fs.absolute(fs.pathJoin(dir, entry));
542
- });
543
- entries.forEach(function _forEach(entry) {
544
- if (fs.isDirectory(entry)) {
545
- entries = entries.concat(self.findTestFiles(entry));
546
- }
547
- });
548
- return entries.filter(function _filter(entry) {
549
- return utils.isJsFile(fs.absolute(fs.pathJoin(dir, entry)));
550
- }).sort();
551
- };
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
+ };
552
616
 
553
- /**
554
- * Formats a message to highlight some parts of it.
555
- *
556
- * @param String message
557
- * @param String style
558
- */
559
- this.formatMessage = function formatMessage(message, style) {
560
- var parts = /^([a-z0-9_\.]+\(\))(.*)/i.exec(message);
561
- if (!parts) {
562
- return message;
563
- }
564
- return this.colorize(parts[1], 'PARAMETER') + this.colorize(parts[2], style);
565
- };
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
+ };
566
639
 
567
- /**
568
- * Retrieves current failure data and all failed cases.
569
- *
570
- * @return Object casedata An object containg information about cases
571
- * @return Number casedata.length The number of failed cases
572
- * @return Array casedata.cases An array of all the failed case objects
573
- */
574
- this.getFailures = function getFailures() {
575
- return {
576
- length: this.testResults.failed,
577
- cases: this.testResults.failures
578
- };
579
- };
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
+ };
580
661
 
581
- /**
582
- * Retrieves current passed data and all passed cases.
583
- *
584
- * @return Object casedata An object containg information about cases
585
- * @return Number casedata.length The number of passed cases
586
- * @return Array casedata.cases An array of all the passed case objects
587
- */
588
- this.getPasses = function getPasses() {
589
- return {
590
- length: this.testResults.passed,
591
- cases: this.testResults.passes
592
- };
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));
593
768
  };
594
769
 
595
- /**
596
- * Writes an info-style formatted message to stdout.
597
- *
598
- * @param String message
599
- */
600
- this.info = function info(message) {
601
- casper.echo(message, 'PARAMETER');
770
+ this.casper.options.onTimeout = function test_onTimeout(timeout) {
771
+ tester.fail(f("Timeout occured (%dms)", timeout));
602
772
  };
603
773
 
604
- /**
605
- * Adds a successful test entry to the stack.
606
- *
607
- * @param String message
608
- */
609
- this.pass = function pass(message) {
610
- return this.assert(true, message, {
611
- type: "pass",
612
- standard: "explicit call to pass()"
613
- });
774
+ this.casper.options.onWaitTimeout = function test_onWaitTimeout(timeout) {
775
+ tester.fail(f("Wait timeout occured (%dms)", timeout));
614
776
  };
777
+ };
615
778
 
616
- /**
617
- * Processes an assertion result by emitting the appropriate event and
618
- * printing result onto the console.
619
- *
620
- * @param Object result An assertion result object
621
- * @return Object The passed assertion result Object
622
- */
623
- this.processAssertionResult = function processAssertionResult(result) {
624
- var eventName, style, status;
625
- if (result.success === true) {
626
- eventName = 'success';
627
- style = 'INFO';
628
- status = this.options.passText;
629
- this.testResults.passed++;
630
- } else {
631
- eventName = 'fail';
632
- style = 'RED_BAR';
633
- status = this.options.failText;
634
- this.testResults.failed++;
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));
635
854
  }
636
- var message = result.message || result.standard;
637
- casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' '));
638
- this.emit(eventName, result);
639
- return result;
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
640
888
  };
889
+ };
641
890
 
642
- /**
643
- * Renders a detailed report for each failed test.
644
- *
645
- * @param Array failures
646
- */
647
- this.renderFailureDetails = function renderFailureDetails(failures) {
648
- if (failures.length === 0) {
649
- return;
650
- }
651
- casper.echo(f("\nDetails for the %d failed test%s:\n", failures.length, failures.length > 1 ? "s" : ""), "PARAMETER");
652
- failures.forEach(function _forEach(failure) {
653
- var type, message, line;
654
- type = failure.type || "unknown";
655
- line = ~~failure.line;
656
- message = failure.message;
657
- casper.echo(f('In %s:%s', failure.file, line));
658
- casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT");
659
- });
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
660
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
+ }
661
915
 
662
- /**
663
- * Render tests results, an optionnaly exit phantomjs.
664
- *
665
- * @param Boolean exit
666
- */
667
- this.renderResults = function renderResults(exit, status, save) {
668
- save = utils.isString(save) ? save : this.options.save;
669
- var total = this.testResults.passed + this.testResults.failed, statusText, style, result;
670
- var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0));
671
- if (total === 0) {
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) {
672
1017
  statusText = this.options.failText;
673
1018
  style = 'RED_BAR';
674
- result = f("%s Looks like you didn't run any test.", statusText);
675
1019
  } else {
676
- if (this.testResults.failed > 0) {
677
- statusText = this.options.failText;
678
- style = 'RED_BAR';
679
- } else {
680
- statusText = this.options.passText;
681
- style = 'GREEN_BAR';
682
- }
683
- result = f('%s %s tests executed, %d passed, %d failed.',
684
- statusText, total, this.testResults.passed, this.testResults.failed);
1020
+ statusText = this.options.passText;
1021
+ style = 'GREEN_BAR';
685
1022
  }
686
- casper.echo(result, style, this.options.pad);
687
- if (this.testResults.failed > 0) {
688
- this.renderFailureDetails(this.testResults.failures);
689
- }
690
- if (save && utils.isFunction(require)) {
691
- try {
692
- fs.write(save, this.exporter.getXML(), 'w');
693
- casper.echo(f('Result log stored in %s', save), 'INFO', 80);
694
- } catch (e) {
695
- casper.echo(f('Unable to write results to %s: %s', save, e), 'ERROR', 80);
696
- }
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");
697
1060
  }
698
- if (exit === true) {
699
- casper.exit(exitStatus);
1061
+ if (fs.isDirectory(path)) {
1062
+ testFiles = testFiles.concat(self.findTestFiles(path));
1063
+ } else if (fs.isFile(path)) {
1064
+ testFiles.push(path);
700
1065
  }
701
- };
702
-
703
- /**
704
- * Runs al suites contained in the paths passed as arguments.
705
- *
706
- */
707
- this.runSuites = function runSuites() {
708
- var testFiles = [], self = this;
709
- if (arguments.length === 0) {
710
- throw new CasperError("runSuites() needs at least one path argument");
711
- }
712
- Array.prototype.forEach.call(arguments, function _forEach(path) {
713
- if (!fs.exists(path)) {
714
- self.bar(f("Path %s doesn't exist", path), "RED_BAR");
715
- }
716
- if (fs.isDirectory(path)) {
717
- testFiles = testFiles.concat(self.findTestFiles(path));
718
- } else if (fs.isFile(path)) {
719
- testFiles.push(path);
720
- }
721
- });
722
- if (testFiles.length === 0) {
723
- this.bar(f("No test file found in %s, aborting.", Array.prototype.slice.call(arguments)), "RED_BAR");
724
- casper.exit(1);
725
- }
726
- var current = 0;
727
- var interval = setInterval(function _check(self) {
728
- if (self.running) {
729
- return;
730
- }
731
- if (current === testFiles.length) {
732
- self.emit('tests.complete');
733
- clearInterval(interval);
734
- } else {
735
- self.runTest(testFiles[current]);
736
- current++;
737
- }
738
- }, 100, this);
739
- };
1066
+ });
740
1067
 
741
- /**
742
- * Runs a test file
743
- *
744
- */
745
- this.runTest = function runTest(testFile) {
746
- this.bar(f('Test file: %s', testFile), 'INFO_BAR');
747
- this.running = true; // this.running is set back to false with done()
748
- this.includes.forEach(function(include) {
749
- phantom.injectJs(include);
750
- });
751
- this.exec(testFile);
752
- };
1068
+ this.loadIncludes.post.forEach(function _forEachPostTest(postTestFile) {
1069
+ testFiles = testFiles.concat(postTestFile);
1070
+ });
753
1071
 
754
- /**
755
- * Tests equality between the two passed arguments.
756
- *
757
- * @param Mixed v1
758
- * @param Mixed v2
759
- * @param Boolean
760
- */
761
- this.testEquals = this.testEqual = function testEquals(v1, v2) {
762
- if (utils.betterTypeOf(v1) !== utils.betterTypeOf(v2)) {
763
- return false;
764
- }
765
- if (utils.isFunction(v1)) {
766
- return v1.toString() === v2.toString();
767
- }
768
- if (v1 instanceof Object && v2 instanceof Object) {
769
- if (Object.keys(v1).length !== Object.keys(v2).length) {
770
- return false;
771
- }
772
- for (var k in v1) {
773
- if (!this.testEquals(v1[k], v2[k])) {
774
- return false;
775
- }
776
- }
777
- return true;
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;
778
1082
  }
779
- return v1 === v2;
780
- };
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
+ };
781
1097
 
782
- /**
783
- * Processes an error caught while running tests contained in a given test
784
- * file.
785
- *
786
- * @param Error|String error The error
787
- * @param String file Test file where the error occured
788
- * @param Number line Line number (optional)
789
- */
790
- this.uncaughtError = function uncaughtError(error, file, line) {
791
- return this.processAssertionResult({
792
- success: false,
793
- type: "uncaughtError",
794
- file: file,
795
- line: ~~line || "unknown",
796
- message: utils.isObject(error) ? error.message : error,
797
- values: {
798
- error: error
799
- }
800
- });
801
- };
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);
802
1108
  };
803
1109
 
804
- // Tester class is an EventEmitter
805
- utils.inherits(Tester, events.EventEmitter);
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
+ };
806
1128
 
807
- exports.Tester = Tester;
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
+ };