Commit 918e9906 918e990687f8a43441bd3c0521cb6f002c6ae85f by Nicolas Perriault

merged with master

2 parents 789f60d0 c335de2e
1 CasperJS Changelog 1 CasperJS Changelog
2 ================== 2 ==================
3 3
4 XXXX-XX-XX, v0.6.11
5 -------------------
6
7 - fixed [#132](https://github.com/n1k0/casperjs/issues/132) - added ability to include js/coffee files using a dedicated option when using the [`casper test` command](http://casperjs.org/testing.html)
8 - fixed [#140](https://github.com/n1k0/casperjs/issues/140) - `casper test` now resolves local paths urls
9 - fixed [#148](https://github.com/n1k0/casperjs/issues/148) - [`utils.isWebPage()`](http://casperjs.org/api.html#utils.isWebPage) was broken
10 - fixed [#149](https://github.com/n1k0/casperjs/issues/149) - [`ClientUtils.fill()`](http://casperjs.org/api.html#casper.fill) was searching elements globally
11 - fixed [#144](https://github.com/n1k0/casperjs/issues/144) - added a [`safeLogs` option](http://casperjs.org/api.html#casper.options) to blur password values in debug logs. **This option is set to `true` by default.**
12 - added [`Casper.userAgent()`](http://casperjs.org/api.html#casper.userAgent) to ease a more dynamic setting of user-agent string
13 - added [`Tester.assertTitleMatch()`](http://casperjs.org/api.html#tester.assertTitleMatch) method
14 - added experimental support of custom headers sending in outgoing request (refs [#137](https://github.com/n1k0/casperjs/issues/137) - PhantomJS 1.6 required)
15 - switched to more standard `.textContent` property to get a node text; this allows a better compatibility of the clientutils bookmarklet with non-webkit browsers
16 - casper modules now all use [javascript strict mode](http://www.nczonline.net/blog/2012/03/13/its-time-to-start-using-javascript-strict-mode/)
17
4 2012-06-04, v0.6.10 18 2012-06-04, v0.6.10
5 ------------------- 19 -------------------
6 20
......
...@@ -4,15 +4,15 @@ CasperJS is a navigation scripting & testing utility for [PhantomJS](http://www. ...@@ -4,15 +4,15 @@ CasperJS is a navigation scripting & testing utility for [PhantomJS](http://www.
4 It eases the process of defining a full navigation scenario and provides useful 4 It eases the process of defining a full navigation scenario and provides useful
5 high-level functions, methods & syntaxic sugar for doing common tasks such as: 5 high-level functions, methods & syntaxic sugar for doing common tasks such as:
6 6
7 - defining & ordering [navigation steps](http://casperjs.org/#quickstart) 7 - defining & ordering [navigation steps](http://casperjs.org/quickstart.html)
8 - [filling forms](http://casperjs.org/#phantom_Casper_fill) 8 - [filling forms](http://casperjs.org/api.html#casper.fill)
9 - [clicking links](http://casperjs.org/#phantom_Casper_click) 9 - [clicking links](http://casperjs.org/api.html#casper.click)
10 - [capturing screenshots](http://casperjs.org/#phantom_Casper_captureSelector) of a page (or an area) 10 - [capturing screenshots](http://casperjs.org/api.html#casper.captureSelector) of a page (or an area)
11 - [making assertions on remote DOM](http://casperjs.org/#phantom_Casper_Tester) 11 - [making assertions on remote DOM](http://casperjs.org/api.html#tester)
12 - [logging](http://casperjs.org/#logging) & [events](http://casperjs.org/#events-filters) 12 - [logging](http://casperjs.org/logging.html) & [events](http://casperjs.org/events-filters.html)
13 - [downloading base64](http://casperjs.org/#phantom_Casper_download) encoded resources, even binary ones 13 - [downloading base64](http://casperjs.org/api.html#casper.download) encoded resources, even binary ones
14 - catching errors and react accordingly 14 - catching errors and react accordingly
15 - writing [functional test suites](http://casperjs.org/#testing), exporting results as JUnit XML (xUnit) 15 - writing [functional test suites](http://casperjs.org/testing.html), exporting results as JUnit XML (xUnit)
16 16
17 Browse the [sample examples repository](https://github.com/n1k0/casperjs/tree/master/samples). 17 Browse the [sample examples repository](https://github.com/n1k0/casperjs/tree/master/samples).
18 Don't hesitate to pull request for any cool example of yours as well! 18 Don't hesitate to pull request for any cool example of yours as well!
......
...@@ -27,259 +27,266 @@ ...@@ -27,259 +27,266 @@
27 * DEALINGS IN THE SOFTWARE. 27 * DEALINGS IN THE SOFTWARE.
28 * 28 *
29 */ 29 */
30
31 /*global console phantom require*/
32
30 if (phantom.version.major !== 1 || phantom.version.minor < 5) { 33 if (phantom.version.major !== 1 || phantom.version.minor < 5) {
31 console.error('CasperJS needs at least PhantomJS v1.5.0'); 34 console.error('CasperJS needs at least PhantomJS v1.5.0');
32 phantom.exit(1); 35 phantom.exit(1);
33 } 36 }
34 37
35 /** 38 (function bootstrap(global) {
36 * Loads and initialize the CasperJS environment. 39 "use strict";
37 */ 40 /**
38 phantom.loadCasper = function loadCasper() { 41 * Loads and initialize the CasperJS environment.
39 // Patching fs 42 */
40 // TODO: watch for these methods being implemented in official fs module 43 phantom.loadCasper = function loadCasper() {
41 var fs = (function _fs(fs) { 44 // Patching fs
42 if (!fs.hasOwnProperty('basename')) { 45 // TODO: watch for these methods being implemented in official fs module
43 fs.basename = function basename(path) { 46 var fs = (function _fs(fs) {
44 return path.replace(/.*\//, ''); 47 if (!fs.hasOwnProperty('basename')) {
45 }; 48 fs.basename = function basename(path) {
46 } 49 return path.replace(/.*\//, '');
47 if (!fs.hasOwnProperty('dirname')) { 50 };
48 fs.dirname = function dirname(path) { 51 }
49 return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, ''); 52 if (!fs.hasOwnProperty('dirname')) {
50 }; 53 fs.dirname = function dirname(path) {
51 } 54 return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, '');
52 if (!fs.hasOwnProperty('isWindows')) { 55 };
53 fs.isWindows = function isWindows() { 56 }
54 var testPath = arguments[0] || this.workingDirectory; 57 if (!fs.hasOwnProperty('isWindows')) {
55 return (/^[a-z]{1,2}:/i).test(testPath) || testPath.indexOf("\\\\") === 0; 58 fs.isWindows = function isWindows() {
56 }; 59 var testPath = arguments[0] || this.workingDirectory;
57 } 60 return (/^[a-z]{1,2}:/i).test(testPath) || testPath.indexOf("\\\\") === 0;
58 if (!fs.hasOwnProperty('pathJoin')) { 61 };
59 fs.pathJoin = function pathJoin() { 62 }
60 return Array.prototype.join.call(arguments, this.separator); 63 if (!fs.hasOwnProperty('pathJoin')) {
61 }; 64 fs.pathJoin = function pathJoin() {
62 } 65 return Array.prototype.join.call(arguments, this.separator);
63 return fs; 66 };
64 })(require('fs')); 67 }
65 68 return fs;
66 // casper root path 69 })(require('fs'));
67 if (!phantom.casperPath) {
68 try {
69 phantom.casperPath = phantom.args.map(function _map(i) {
70 var match = i.match(/^--casper-path=(.*)/);
71 if (match) {
72 return fs.absolute(match[1]);
73 }
74 }).filter(function _filter(path) {
75 return fs.isDirectory(path);
76 }).pop();
77 } catch (e) {}
78 }
79
80 if (!phantom.casperPath) {
81 console.error("Couldn't find nor compute phantom.casperPath, exiting.");
82 phantom.exit(1);
83 }
84 70
85 // Embedded, up-to-date, validatable & controlable CoffeeScript 71 // casper root path
86 phantom.injectJs(fs.pathJoin(phantom.casperPath, 'modules', 'vendors', 'coffee-script.js')); 72 if (!phantom.casperPath) {
73 try {
74 phantom.casperPath = phantom.args.map(function _map(i) {
75 var match = i.match(/^--casper-path=(.*)/);
76 if (match) {
77 return fs.absolute(match[1]);
78 }
79 }).filter(function _filter(path) {
80 return fs.isDirectory(path);
81 }).pop();
82 } catch (e) {}
83 }
87 84
88 // custom global CasperError 85 if (!phantom.casperPath) {
89 window.CasperError = function CasperError(msg) { 86 console.error("Couldn't find nor compute phantom.casperPath, exiting.");
90 Error.call(this); 87 phantom.exit(1);
91 try {
92 // let's get where this error has been thrown from, if we can
93 this._from = arguments.callee.caller.name;
94 } catch (e) {
95 this._from = "anonymous";
96 } 88 }
97 this.message = msg;
98 this.name = 'CasperError';
99 };
100 89
101 // standard Error prototype inheritance 90 // Embedded, up-to-date, validatable & controlable CoffeeScript
102 window.CasperError.prototype = Object.getPrototypeOf(new Error()); 91 phantom.injectJs(fs.pathJoin(phantom.casperPath, 'modules', 'vendors', 'coffee-script.js'));
103 92
104 // CasperJS version, extracted from package.json - see http://semver.org/ 93 // custom global CasperError
105 phantom.casperVersion = (function getVersion(path) { 94 global.CasperError = function CasperError(msg) {
106 var parts, patchPart, pkg, pkgFile; 95 Error.call(this);
107 var fs = require('fs'); 96 try {
108 pkgFile = fs.absolute(fs.pathJoin(path, 'package.json')); 97 // let's get where this error has been thrown from, if we can
109 if (!fs.exists(pkgFile)) { 98 this._from = arguments.callee.caller.name;
110 throw new CasperError('Cannot find package.json at ' + pkgFile); 99 } catch (e) {
111 } 100 this._from = "anonymous";
112 try {
113 pkg = JSON.parse(require('fs').read(pkgFile));
114 } catch (e) {
115 throw new CasperError('Cannot read package file contents: ' + e);
116 }
117 parts = pkg.version.trim().split(".");
118 if (parts < 3) {
119 throw new CasperError("Invalid version number");
120 }
121 patchPart = parts[2].split('-');
122 return {
123 major: ~~parts[0] || 0,
124 minor: ~~parts[1] || 0,
125 patch: ~~patchPart[0] || 0,
126 ident: patchPart[1] || "",
127 toString: function toString() {
128 var version = [this.major, this.minor, this.patch].join('.');
129 if (this.ident) {
130 version = [version, this.ident].join('-');
131 }
132 return version;
133 } 101 }
102 this.message = msg;
103 this.name = 'CasperError';
134 }; 104 };
135 })(phantom.casperPath);
136 105
137 /** 106 // standard Error prototype inheritance
138 * Retrieves the javascript source code from a given .js or .coffee file. 107 global.CasperError.prototype = Object.getPrototypeOf(new Error());
139 *
140 * @param String file The path to the file
141 * @param Function|null onError An error callback (optional)
142 */
143 phantom.getScriptCode = function getScriptCode(file, onError) {
144 var scriptCode = fs.read(file);
145 if (/\.coffee$/i.test(file)) {
146 scriptCode = CoffeeScript.compile(scriptCode);
147 }
148 return scriptCode;
149 };
150 108
151 /** 109 // CasperJS version, extracted from package.json - see http://semver.org/
152 * Patching require() to allow loading of other modules than PhantomJS' 110 phantom.casperVersion = (function getVersion(path) {
153 * builtin ones. 111 var parts, patchPart, pkg, pkgFile;
154 * Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/ 112 var fs = require('fs');
155 * TODO: remove when PhantomJS has full module support 113 pkgFile = fs.absolute(fs.pathJoin(path, 'package.json'));
156 */ 114 if (!fs.exists(pkgFile)) {
157 require = (function _require(require, requireDir) { 115 throw new global.CasperError('Cannot find package.json at ' + pkgFile);
158 var phantomBuiltins = ['fs', 'webpage', 'webserver', 'system'];
159 var phantomRequire = phantom.__orig__require = require;
160 var requireCache = {};
161 return function _require(path) {
162 var i, dir, paths = [],
163 fileGuesses = [],
164 file,
165 module = {
166 exports: {}
167 };
168 if (phantomBuiltins.indexOf(path) !== -1) {
169 return phantomRequire(path);
170 }
171 if (path[0] === '.') {
172 paths.push.apply(paths, [
173 fs.absolute(path),
174 fs.absolute(fs.pathJoin(requireDir, path))
175 ]);
176 } else if (path[0] === '/') {
177 paths.push(path);
178 } else {
179 dir = fs.absolute(requireDir);
180 while (dir !== '' && dir.lastIndexOf(':') !== dir.length - 1) {
181 // nodejs compatibility
182 paths.push(fs.pathJoin(dir, 'node_modules', path));
183 dir = fs.dirname(dir);
184 }
185 paths.push(fs.pathJoin(requireDir, 'lib', path));
186 paths.push(fs.pathJoin(requireDir, 'modules', path));
187 }
188 paths.forEach(function _forEach(testPath) {
189 fileGuesses.push.apply(fileGuesses, [
190 testPath,
191 testPath + '.js',
192 testPath + '.coffee',
193 fs.pathJoin(testPath, 'index.js'),
194 fs.pathJoin(testPath, 'index.coffee'),
195 fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
196 fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
197 ]);
198 });
199 file = null;
200 for (i = 0; i < fileGuesses.length && !file; ++i) {
201 if (fs.isFile(fileGuesses[i])) {
202 file = fileGuesses[i];
203 }
204 }
205 if (!file) {
206 throw new Error("CasperJS couldn't find module " + path);
207 }
208 if (file in requireCache) {
209 return requireCache[file].exports;
210 } 116 }
211 var scriptCode = phantom.getScriptCode(file);
212 var fn = new Function('__file__', 'require', 'module', 'exports', scriptCode);
213 try { 117 try {
214 fn(file, _require, module, module.exports); 118 pkg = JSON.parse(require('fs').read(pkgFile));
215 } catch (e) { 119 } catch (e) {
216 var error = new CasperError('__mod_error(' + path + '):: ' + e); 120 throw new global.CasperError('Cannot read package file contents: ' + e);
217 error.file = file;
218 throw error;
219 } 121 }
220 requireCache[file] = module; 122 parts = pkg.version.trim().split(".");
221 return module.exports; 123 if (parts < 3) {
124 throw new global.CasperError("Invalid version number");
125 }
126 patchPart = parts[2].split('-');
127 return {
128 major: ~~parts[0] || 0,
129 minor: ~~parts[1] || 0,
130 patch: ~~patchPart[0] || 0,
131 ident: patchPart[1] || "",
132 toString: function toString() {
133 var version = [this.major, this.minor, this.patch].join('.');
134 if (this.ident) {
135 version = [version, this.ident].join('-');
136 }
137 return version;
138 }
139 };
140 })(phantom.casperPath);
141
142 /**
143 * Retrieves the javascript source code from a given .js or .coffee file.
144 *
145 * @param String file The path to the file
146 * @param Function|null onError An error callback (optional)
147 */
148 phantom.getScriptCode = function getScriptCode(file, onError) {
149 var scriptCode = fs.read(file);
150 if (/\.coffee$/i.test(file)) {
151 /*global CoffeeScript*/
152 scriptCode = CoffeeScript.compile(scriptCode);
153 }
154 return scriptCode;
222 }; 155 };
223 })(require, phantom.casperPath);
224 156
225 // BC < 0.6 157 /**
226 phantom.Casper = require('casper').Casper; 158 * Patching require() to allow loading of other modules than PhantomJS'
159 * builtin ones.
160 * Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/
161 * TODO: remove when PhantomJS has full module support
162 */
163 require = (function _require(require, requireDir) {
164 var phantomBuiltins = ['fs', 'webpage', 'webserver', 'system'];
165 var phantomRequire = phantom.__orig__require = require;
166 var requireCache = {};
167 return function _require(path) {
168 var i, dir, paths = [],
169 fileGuesses = [],
170 file,
171 module = {
172 exports: {}
173 };
174 if (phantomBuiltins.indexOf(path) !== -1) {
175 return phantomRequire(path);
176 }
177 if (path[0] === '.') {
178 paths.push.apply(paths, [
179 fs.absolute(path),
180 fs.absolute(fs.pathJoin(requireDir, path))
181 ]);
182 } else if (path[0] === '/') {
183 paths.push(path);
184 } else {
185 dir = fs.absolute(requireDir);
186 while (dir !== '' && dir.lastIndexOf(':') !== dir.length - 1) {
187 // nodejs compatibility
188 paths.push(fs.pathJoin(dir, 'node_modules', path));
189 dir = fs.dirname(dir);
190 }
191 paths.push(fs.pathJoin(requireDir, 'lib', path));
192 paths.push(fs.pathJoin(requireDir, 'modules', path));
193 }
194 paths.forEach(function _forEach(testPath) {
195 fileGuesses.push.apply(fileGuesses, [
196 testPath,
197 testPath + '.js',
198 testPath + '.coffee',
199 fs.pathJoin(testPath, 'index.js'),
200 fs.pathJoin(testPath, 'index.coffee'),
201 fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
202 fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
203 ]);
204 });
205 file = null;
206 for (i = 0; i < fileGuesses.length && !file; ++i) {
207 if (fs.isFile(fileGuesses[i])) {
208 file = fileGuesses[i];
209 }
210 }
211 if (!file) {
212 throw new Error("CasperJS couldn't find module " + path);
213 }
214 if (file in requireCache) {
215 return requireCache[file].exports;
216 }
217 var scriptCode = phantom.getScriptCode(file);
218 var fn = new Function('__file__', 'require', 'module', 'exports', scriptCode);
219 try {
220 fn(file, _require, module, module.exports);
221 } catch (e) {
222 var error = new global.CasperError('__mod_error(' + path + '):: ' + e);
223 error.file = file;
224 throw error;
225 }
226 requireCache[file] = module;
227 return module.exports;
228 };
229 })(require, phantom.casperPath);
227 230
228 // casper cli args 231 // BC < 0.6
229 phantom.casperArgs = require('cli').parse(phantom.args); 232 phantom.Casper = require('casper').Casper;
230 233
231 // loaded status 234 // casper cli args
232 phantom.casperLoaded = true; 235 phantom.casperArgs = require('cli').parse(phantom.args);
233 };
234 236
235 /** 237 // loaded status
236 * Initializes the CasperJS Command Line Interface. 238 phantom.casperLoaded = true;
237 */ 239 };
238 phantom.initCasperCli = function initCasperCli() {
239 var fs = require("fs");
240 240
241 if (!!phantom.casperArgs.options.version) { 241 /**
242 console.log(phantom.casperVersion.toString()); 242 * Initializes the CasperJS Command Line Interface.
243 phantom.exit(0); 243 */
244 } else if (phantom.casperArgs.get(0) === "test") { 244 phantom.initCasperCli = function initCasperCli() {
245 phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js')); 245 var fs = require("fs");
246 phantom.casperArgs.drop("test");
247 } else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) {
248 var phantomVersion = [phantom.version.major, phantom.version.minor, phantom.version.patch].join('.');
249 var f = require("utils").format;
250 console.log(f('CasperJS version %s at %s, using PhantomJS version %s',
251 phantom.casperVersion.toString(),
252 phantom.casperPath, phantomVersion));
253 console.log(fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt')));
254 phantom.exit(0);
255 }
256 246
247 if (!!phantom.casperArgs.options.version) {
248 console.log(phantom.casperVersion.toString());
249 phantom.exit(0);
250 } else if (phantom.casperArgs.get(0) === "test") {
251 phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js'));
252 phantom.casperArgs.drop("test");
253 } else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) {
254 var phantomVersion = [phantom.version.major, phantom.version.minor, phantom.version.patch].join('.');
255 var f = require("utils").format;
256 console.log(f('CasperJS version %s at %s, using PhantomJS version %s',
257 phantom.casperVersion.toString(),
258 phantom.casperPath, phantomVersion));
259 console.log(fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt')));
260 phantom.exit(0);
261 }
257 262
258 if (!phantom.casperScript) {
259 phantom.casperScript = phantom.casperArgs.get(0);
260 }
261 263
262 if (!fs.isFile(phantom.casperScript)) { 264 if (!phantom.casperScript) {
263 console.error('Unable to open file: ' + phantom.casperScript); 265 phantom.casperScript = phantom.casperArgs.get(0);
264 phantom.exit(1); 266 }
265 }
266 267
267 // filter out the called script name from casper args 268 if (!fs.isFile(phantom.casperScript)) {
268 phantom.casperArgs.drop(phantom.casperScript); 269 console.error('Unable to open file: ' + phantom.casperScript);
270 phantom.exit(1);
271 }
269 272
270 // passed casperjs script execution 273 // filter out the called script name from casper args
271 phantom.injectJs(phantom.casperScript); 274 phantom.casperArgs.drop(phantom.casperScript);
272 };
273 275
274 if (!phantom.casperLoaded) { 276 // passed casperjs script execution
275 try { 277 phantom.injectJs(phantom.casperScript);
276 phantom.loadCasper(); 278 };
277 } catch (e) { 279
278 console.error("Unable to load casper environment: " + e); 280 if (!phantom.casperLoaded) {
279 phantom.exit(); 281 try {
282 phantom.loadCasper();
283 } catch (e) {
284 console.error("Unable to load casper environment: " + e);
285 phantom.exit();
286 }
280 } 287 }
281 }
282 288
283 if (true === phantom.casperArgs.get('cli')) { 289 if (true === phantom.casperArgs.get('cli')) {
284 phantom.initCasperCli(); 290 phantom.initCasperCli();
285 } 291 }
292 })(window);
......
...@@ -48,6 +48,6 @@ CASPER_COMMAND.extend(CASPER_ARGS) ...@@ -48,6 +48,6 @@ CASPER_COMMAND.extend(CASPER_ARGS)
48 48
49 try: 49 try:
50 os.execvp(CASPER_COMMAND[0], CASPER_COMMAND) 50 os.execvp(CASPER_COMMAND[0], CASPER_COMMAND)
51 except OSError, err: 51 except OSError as err:
52 print('Fatal: %s; did you install phantomjs?' % err) 52 print(('Fatal: %s; did you install phantomjs?' % err))
53 sys.exit(1) 53 sys.exit(1)
......
1 Subproject commit ead2191ceb086cd5665e18a822f72689d90a6c3d 1 Subproject commit 76cc32f262a3ef924a67f33bb17432e3a5c33e03
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
28 * 28 *
29 */ 29 */
30 30
31 /*global CasperError console exports phantom require*/
32
31 var colorizer = require('colorizer'); 33 var colorizer = require('colorizer');
32 var events = require('events'); 34 var events = require('events');
33 var fs = require('fs'); 35 var fs = require('fs');
...@@ -37,11 +39,24 @@ var tester = require('tester'); ...@@ -37,11 +39,24 @@ var tester = require('tester');
37 var utils = require('utils'); 39 var utils = require('utils');
38 var f = utils.format; 40 var f = utils.format;
39 41
42
43 var defaultUserAgent = phantom.defaultPageSettings.userAgent
44 .replace('PhantomJS', f("CasperJS/%s", phantom.casperVersion) + '+Phantomjs');
45
40 exports.create = function create(options) { 46 exports.create = function create(options) {
47 "use strict";
41 return new Casper(options); 48 return new Casper(options);
42 }; 49 };
43 50
44 exports.selectXPath = function selectXPath(expression) { 51 /**
52 * Shortcut to build an XPath selector object.
53 *
54 * @param String expression The XPath expression
55 * @return Object
56 * @see http://casperjs.org/selectors.html
57 */
58 function selectXPath(expression) {
59 "use strict";
45 return { 60 return {
46 type: 'xpath', 61 type: 'xpath',
47 path: expression, 62 path: expression,
...@@ -49,7 +64,8 @@ exports.selectXPath = function selectXPath(expression) { ...@@ -49,7 +64,8 @@ exports.selectXPath = function selectXPath(expression) {
49 return this.type + ' selector: ' + this.path; 64 return this.type + ' selector: ' + this.path;
50 } 65 }
51 }; 66 };
52 }; 67 }
68 exports.selectXPath = selectXPath;
53 69
54 /** 70 /**
55 * Main Casper object. 71 * Main Casper object.
...@@ -57,10 +73,9 @@ exports.selectXPath = function selectXPath(expression) { ...@@ -57,10 +73,9 @@ exports.selectXPath = function selectXPath(expression) {
57 * @param Object options Casper options 73 * @param Object options Casper options
58 */ 74 */
59 var Casper = function Casper(options) { 75 var Casper = function Casper(options) {
60 var DEFAULT_DIE_MESSAGE = "Suite explicitely interrupted without any message given."; 76 "use strict";
61 var DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1";
62 // init & checks 77 // init & checks
63 if (!(this instanceof arguments.callee)) { 78 if (!(this instanceof Casper)) {
64 return new Casper(options); 79 return new Casper(options);
65 } 80 }
66 // default options 81 // default options
...@@ -70,6 +85,7 @@ var Casper = function Casper(options) { ...@@ -70,6 +85,7 @@ var Casper = function Casper(options) {
70 exitOnError: true, 85 exitOnError: true,
71 logLevel: "error", 86 logLevel: "error",
72 httpStatusHandlers: {}, 87 httpStatusHandlers: {},
88 safeLogs: true,
73 onAlert: null, 89 onAlert: null,
74 onDie: null, 90 onDie: null,
75 onError: null, 91 onError: null,
...@@ -83,7 +99,7 @@ var Casper = function Casper(options) { ...@@ -83,7 +99,7 @@ var Casper = function Casper(options) {
83 page: null, 99 page: null,
84 pageSettings: { 100 pageSettings: {
85 localToRemoteUrlAccessEnabled: true, 101 localToRemoteUrlAccessEnabled: true,
86 userAgent: DEFAULT_USER_AGENT 102 userAgent: defaultUserAgent
87 }, 103 },
88 stepTimeout: null, 104 stepTimeout: null,
89 timeout: null, 105 timeout: null,
...@@ -150,7 +166,7 @@ var Casper = function Casper(options) { ...@@ -150,7 +166,7 @@ var Casper = function Casper(options) {
150 166
151 // deprecated feature event handler 167 // deprecated feature event handler
152 this.on('deprecated', function onDeprecated(message) { 168 this.on('deprecated', function onDeprecated(message) {
153 this.echo('[deprecated] ' + message, 'COMMENT'); 169 this.warn('[deprecated] ' + message);
154 }); 170 });
155 171
156 // dispatching an event when instance has been constructed 172 // dispatching an event when instance has been constructed
...@@ -166,6 +182,7 @@ utils.inherits(Casper, events.EventEmitter); ...@@ -166,6 +182,7 @@ utils.inherits(Casper, events.EventEmitter);
166 * @return Casper 182 * @return Casper
167 */ 183 */
168 Casper.prototype.back = function back() { 184 Casper.prototype.back = function back() {
185 "use strict";
169 return this.then(function _step() { 186 return this.then(function _step() {
170 this.emit('back'); 187 this.emit('back');
171 this.evaluate(function _evaluate() { 188 this.evaluate(function _evaluate() {
...@@ -186,8 +203,9 @@ Casper.prototype.back = function back() { ...@@ -186,8 +203,9 @@ Casper.prototype.back = function back() {
186 * @return string Base64 encoded result 203 * @return string Base64 encoded result
187 */ 204 */
188 Casper.prototype.base64encode = function base64encode(url, method, data) { 205 Casper.prototype.base64encode = function base64encode(url, method, data) {
206 "use strict";
189 return this.evaluate(function _evaluate(url, method, data) { 207 return this.evaluate(function _evaluate(url, method, data) {
190 return __utils__.getBase64(url, method, data); 208 return window.__utils__.getBase64(url, method, data);
191 }, { url: url, method: method, data: data }); 209 }, { url: url, method: method, data: data });
192 }; 210 };
193 211
...@@ -202,6 +220,7 @@ Casper.prototype.base64encode = function base64encode(url, method, data) { ...@@ -202,6 +220,7 @@ Casper.prototype.base64encode = function base64encode(url, method, data) {
202 * @return Casper 220 * @return Casper
203 */ 221 */
204 Casper.prototype.capture = function capture(targetFile, clipRect) { 222 Casper.prototype.capture = function capture(targetFile, clipRect) {
223 "use strict";
205 var previousClipRect; 224 var previousClipRect;
206 targetFile = fs.absolute(targetFile); 225 targetFile = fs.absolute(targetFile);
207 if (clipRect) { 226 if (clipRect) {
...@@ -234,6 +253,7 @@ Casper.prototype.capture = function capture(targetFile, clipRect) { ...@@ -234,6 +253,7 @@ Casper.prototype.capture = function capture(targetFile, clipRect) {
234 * @return Casper 253 * @return Casper
235 */ 254 */
236 Casper.prototype.captureSelector = function captureSelector(targetFile, selector) { 255 Casper.prototype.captureSelector = function captureSelector(targetFile, selector) {
256 "use strict";
237 return this.capture(targetFile, this.getElementBounds(selector)); 257 return this.capture(targetFile, this.getElementBounds(selector));
238 }; 258 };
239 259
...@@ -244,6 +264,7 @@ Casper.prototype.captureSelector = function captureSelector(targetFile, selector ...@@ -244,6 +264,7 @@ Casper.prototype.captureSelector = function captureSelector(targetFile, selector
244 * @param function onComplete An options callback to apply on completion 264 * @param function onComplete An options callback to apply on completion
245 */ 265 */
246 Casper.prototype.checkStep = function checkStep(self, onComplete) { 266 Casper.prototype.checkStep = function checkStep(self, onComplete) {
267 "use strict";
247 if (self.pendingWait || self.loadInProgress) { 268 if (self.pendingWait || self.loadInProgress) {
248 return; 269 return;
249 } 270 }
...@@ -274,6 +295,7 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) { ...@@ -274,6 +295,7 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) {
274 * @return Casper 295 * @return Casper
275 */ 296 */
276 Casper.prototype.clear = function clear() { 297 Casper.prototype.clear = function clear() {
298 "use strict";
277 this.page.content = ''; 299 this.page.content = '';
278 return this; 300 return this;
279 }; 301 };
...@@ -288,6 +310,7 @@ Casper.prototype.clear = function clear() { ...@@ -288,6 +310,7 @@ Casper.prototype.clear = function clear() {
288 * @return Boolean 310 * @return Boolean
289 */ 311 */
290 Casper.prototype.click = function click(selector) { 312 Casper.prototype.click = function click(selector) {
313 "use strict";
291 return this.mouseEvent('click', selector); 314 return this.mouseEvent('click', selector);
292 }; 315 };
293 316
...@@ -296,10 +319,14 @@ Casper.prototype.click = function click(selector) { ...@@ -296,10 +319,14 @@ Casper.prototype.click = function click(selector) {
296 * element matching this label will be selected, so use with caution. 319 * element matching this label will be selected, so use with caution.
297 * 320 *
298 * @param String label Element innerText value 321 * @param String label Element innerText value
322 * @param String tag An element tag name (eg. `a` or `button`) (optional)
299 * @return Boolean 323 * @return Boolean
300 */ 324 */
301 Casper.prototype.clickLabel = function clickLabel(label) { 325 Casper.prototype.clickLabel = function clickLabel(label, tag) {
302 var selector = exports.selectXPath('//*[text()="' + label.toString() + '"]'); 326 "use strict";
327 tag = tag || "*";
328 var escapedLabel = label.toString().replace(/"/g, '\\"');
329 var selector = selectXPath(f('//%s[text()="%s"]', tag, escapedLabel));
303 return this.click(selector); 330 return this.click(selector);
304 }; 331 };
305 332
...@@ -311,6 +338,7 @@ Casper.prototype.clickLabel = function clickLabel(label) { ...@@ -311,6 +338,7 @@ Casper.prototype.clickLabel = function clickLabel(label) {
311 * @return Function The final step function 338 * @return Function The final step function
312 */ 339 */
313 Casper.prototype.createStep = function createStep(fn, options) { 340 Casper.prototype.createStep = function createStep(fn, options) {
341 "use strict";
314 if (!utils.isFunction(fn)) { 342 if (!utils.isFunction(fn)) {
315 throw new CasperError("createStep(): a step definition must be a function"); 343 throw new CasperError("createStep(): a step definition must be a function");
316 } 344 }
...@@ -325,6 +353,7 @@ Casper.prototype.createStep = function createStep(fn, options) { ...@@ -325,6 +353,7 @@ Casper.prototype.createStep = function createStep(fn, options) {
325 * @return Casper 353 * @return Casper
326 */ 354 */
327 Casper.prototype.debugHTML = function debugHTML() { 355 Casper.prototype.debugHTML = function debugHTML() {
356 "use strict";
328 this.echo(this.page.content); 357 this.echo(this.page.content);
329 return this; 358 return this;
330 }; 359 };
...@@ -335,8 +364,9 @@ Casper.prototype.debugHTML = function debugHTML() { ...@@ -335,8 +364,9 @@ Casper.prototype.debugHTML = function debugHTML() {
335 * @return Casper 364 * @return Casper
336 */ 365 */
337 Casper.prototype.debugPage = function debugPage() { 366 Casper.prototype.debugPage = function debugPage() {
367 "use strict";
338 this.echo(this.evaluate(function _evaluate() { 368 this.echo(this.evaluate(function _evaluate() {
339 return document.body.innerText; 369 return document.body.textContent || document.body.innerText;
340 })); 370 }));
341 return this; 371 return this;
342 }; 372 };
...@@ -349,9 +379,12 @@ Casper.prototype.debugPage = function debugPage() { ...@@ -349,9 +379,12 @@ Casper.prototype.debugPage = function debugPage() {
349 * @return Casper 379 * @return Casper
350 */ 380 */
351 Casper.prototype.die = function die(message, status) { 381 Casper.prototype.die = function die(message, status) {
382 "use strict";
352 this.result.status = "error"; 383 this.result.status = "error";
353 this.result.time = new Date().getTime() - this.startTime; 384 this.result.time = new Date().getTime() - this.startTime;
354 message = utils.isString(message) && message.length > 0 ? message : DEFAULT_DIE_MESSAGE; 385 if (!utils.isString(message) || !message.length) {
386 message = "Suite explicitely interrupted without any message given.";
387 }
355 this.log(message, "error"); 388 this.log(message, "error");
356 this.emit('die', message, status); 389 this.emit('die', message, status);
357 if (utils.isFunction(this.options.onDie)) { 390 if (utils.isFunction(this.options.onDie)) {
...@@ -365,10 +398,13 @@ Casper.prototype.die = function die(message, status) { ...@@ -365,10 +398,13 @@ Casper.prototype.die = function die(message, status) {
365 * 398 *
366 * @param String url The url of the resource to download 399 * @param String url The url of the resource to download
367 * @param String targetPath The destination file path 400 * @param String targetPath The destination file path
401 * @param String method The HTTP method to use (default: GET)
402 * @param String data Optional data to pass performing the request
368 * @return Casper 403 * @return Casper
369 */ 404 */
370 Casper.prototype.download = function download(url, targetPath, method, data) { 405 Casper.prototype.download = function download(url, targetPath, method, data) {
371 var cu = require('clientutils').create(); 406 "use strict";
407 var cu = require('clientutils').create(this.options);
372 try { 408 try {
373 fs.write(targetPath, cu.decode(this.base64encode(url, method, data)), 'wb'); 409 fs.write(targetPath, cu.decode(this.base64encode(url, method, data)), 'wb');
374 this.emit('downloaded.file', targetPath); 410 this.emit('downloaded.file', targetPath);
...@@ -388,6 +424,7 @@ Casper.prototype.download = function download(url, targetPath, method, data) { ...@@ -388,6 +424,7 @@ Casper.prototype.download = function download(url, targetPath, method, data) {
388 * @return Casper 424 * @return Casper
389 */ 425 */
390 Casper.prototype.each = function each(array, fn) { 426 Casper.prototype.each = function each(array, fn) {
427 "use strict";
391 if (!utils.isArray(array)) { 428 if (!utils.isArray(array)) {
392 this.log("each() only works with arrays", "error"); 429 this.log("each() only works with arrays", "error");
393 return this; 430 return this;
...@@ -409,6 +446,7 @@ Casper.prototype.each = function each(array, fn) { ...@@ -409,6 +446,7 @@ Casper.prototype.each = function each(array, fn) {
409 * @return Casper 446 * @return Casper
410 */ 447 */
411 Casper.prototype.echo = function echo(text, style, pad) { 448 Casper.prototype.echo = function echo(text, style, pad) {
449 "use strict";
412 var message = style ? this.colorizer.colorize(text, style, pad) : text; 450 var message = style ? this.colorizer.colorize(text, style, pad) : text;
413 console.log(this.filter('echo.message', message) || message); 451 console.log(this.filter('echo.message', message) || message);
414 return this; 452 return this;
...@@ -438,6 +476,7 @@ Casper.prototype.echo = function echo(text, style, pad) { ...@@ -438,6 +476,7 @@ Casper.prototype.echo = function echo(text, style, pad) {
438 * @see WebPage#evaluate 476 * @see WebPage#evaluate
439 */ 477 */
440 Casper.prototype.evaluate = function evaluate(fn, context) { 478 Casper.prototype.evaluate = function evaluate(fn, context) {
479 "use strict";
441 // ensure client utils are always injected 480 // ensure client utils are always injected
442 this.injectClientUtils(); 481 this.injectClientUtils();
443 // function processing 482 // function processing
...@@ -455,6 +494,7 @@ Casper.prototype.evaluate = function evaluate(fn, context) { ...@@ -455,6 +494,7 @@ Casper.prototype.evaluate = function evaluate(fn, context) {
455 * @return Casper 494 * @return Casper
456 */ 495 */
457 Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message) { 496 Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message) {
497 "use strict";
458 if (!this.evaluate(fn)) { 498 if (!this.evaluate(fn)) {
459 return this.die(message); 499 return this.die(message);
460 } 500 }
...@@ -469,8 +509,9 @@ Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message) { ...@@ -469,8 +509,9 @@ Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message) {
469 * @return Boolean 509 * @return Boolean
470 */ 510 */
471 Casper.prototype.exists = function exists(selector) { 511 Casper.prototype.exists = function exists(selector) {
512 "use strict";
472 return this.evaluate(function _evaluate(selector) { 513 return this.evaluate(function _evaluate(selector) {
473 return __utils__.exists(selector); 514 return window.__utils__.exists(selector);
474 }, { selector: selector }); 515 }, { selector: selector });
475 }; 516 };
476 517
...@@ -481,6 +522,7 @@ Casper.prototype.exists = function exists(selector) { ...@@ -481,6 +522,7 @@ Casper.prototype.exists = function exists(selector) {
481 * @return Casper 522 * @return Casper
482 */ 523 */
483 Casper.prototype.exit = function exit(status) { 524 Casper.prototype.exit = function exit(status) {
525 "use strict";
484 this.emit('exit', status); 526 this.emit('exit', status);
485 phantom.exit(status); 527 phantom.exit(status);
486 return this; 528 return this;
...@@ -494,8 +536,9 @@ Casper.prototype.exit = function exit(status) { ...@@ -494,8 +536,9 @@ Casper.prototype.exit = function exit(status) {
494 * @return String 536 * @return String
495 */ 537 */
496 Casper.prototype.fetchText = function fetchText(selector) { 538 Casper.prototype.fetchText = function fetchText(selector) {
539 "use strict";
497 return this.evaluate(function _evaluate(selector) { 540 return this.evaluate(function _evaluate(selector) {
498 return __utils__.fetchText(selector); 541 return window.__utils__.fetchText(selector);
499 }, { selector: selector }); 542 }, { selector: selector });
500 }; 543 };
501 544
...@@ -507,13 +550,14 @@ Casper.prototype.fetchText = function fetchText(selector) { ...@@ -507,13 +550,14 @@ Casper.prototype.fetchText = function fetchText(selector) {
507 * @param Boolean submit Submit the form? 550 * @param Boolean submit Submit the form?
508 */ 551 */
509 Casper.prototype.fill = function fill(selector, vals, submit) { 552 Casper.prototype.fill = function fill(selector, vals, submit) {
553 "use strict";
510 submit = submit === true ? submit : false; 554 submit = submit === true ? submit : false;
511 if (!utils.isObject(vals)) { 555 if (!utils.isObject(vals)) {
512 throw new CasperError("Form values must be provided as an object"); 556 throw new CasperError("Form values must be provided as an object");
513 } 557 }
514 this.emit('fill', selector, vals, submit); 558 this.emit('fill', selector, vals, submit);
515 var fillResults = this.evaluate(function _evaluate(selector, values) { 559 var fillResults = this.evaluate(function _evaluate(selector, values) {
516 return __utils__.fill(selector, values); 560 return window.__utils__.fill(selector, values);
517 }, { 561 }, {
518 selector: selector, 562 selector: selector,
519 values: vals 563 values: vals
...@@ -548,10 +592,10 @@ Casper.prototype.fill = function fill(selector, vals, submit) { ...@@ -548,10 +592,10 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
548 // Form submission? 592 // Form submission?
549 if (submit) { 593 if (submit) {
550 this.evaluate(function _evaluate(selector) { 594 this.evaluate(function _evaluate(selector) {
551 var form = __utils__.findOne(selector); 595 var form = window.__utils__.findOne(selector);
552 var method = (form.getAttribute('method') || "GET").toUpperCase(); 596 var method = (form.getAttribute('method') || "GET").toUpperCase();
553 var action = form.getAttribute('action') || "unknown"; 597 var action = form.getAttribute('action') || "unknown";
554 __utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info'); 598 window.__utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info');
555 if (typeof form.submit === "function") { 599 if (typeof form.submit === "function") {
556 form.submit(); 600 form.submit();
557 } else { 601 } else {
...@@ -568,6 +612,7 @@ Casper.prototype.fill = function fill(selector, vals, submit) { ...@@ -568,6 +612,7 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
568 * @return Casper 612 * @return Casper
569 */ 613 */
570 Casper.prototype.forward = function forward(then) { 614 Casper.prototype.forward = function forward(then) {
615 "use strict";
571 return this.then(function _step() { 616 return this.then(function _step() {
572 this.emit('forward'); 617 this.emit('forward');
573 this.evaluate(function _evaluate() { 618 this.evaluate(function _evaluate() {
...@@ -583,6 +628,7 @@ Casper.prototype.forward = function forward(then) { ...@@ -583,6 +628,7 @@ Casper.prototype.forward = function forward(then) {
583 * @return Object 628 * @return Object
584 */ 629 */
585 Casper.prototype.getColorizer = function getColorizer() { 630 Casper.prototype.getColorizer = function getColorizer() {
631 "use strict";
586 return colorizer.create(this.options.colorizerType || 'Colorizer'); 632 return colorizer.create(this.options.colorizerType || 'Colorizer');
587 }; 633 };
588 634
...@@ -592,6 +638,7 @@ Casper.prototype.getColorizer = function getColorizer() { ...@@ -592,6 +638,7 @@ Casper.prototype.getColorizer = function getColorizer() {
592 * @return String 638 * @return String
593 */ 639 */
594 Casper.prototype.getCurrentUrl = function getCurrentUrl() { 640 Casper.prototype.getCurrentUrl = function getCurrentUrl() {
641 "use strict";
595 return decodeURIComponent(this.evaluate(function _evaluate() { 642 return decodeURIComponent(this.evaluate(function _evaluate() {
596 return document.location.href; 643 return document.location.href;
597 })); 644 }));
...@@ -604,11 +651,12 @@ Casper.prototype.getCurrentUrl = function getCurrentUrl() { ...@@ -604,11 +651,12 @@ Casper.prototype.getCurrentUrl = function getCurrentUrl() {
604 * @return Object 651 * @return Object
605 */ 652 */
606 Casper.prototype.getElementBounds = function getElementBounds(selector) { 653 Casper.prototype.getElementBounds = function getElementBounds(selector) {
654 "use strict";
607 if (!this.exists(selector)) { 655 if (!this.exists(selector)) {
608 throw new CasperError("No element matching selector found: " + selector); 656 throw new CasperError("No element matching selector found: " + selector);
609 } 657 }
610 var clipRect = this.evaluate(function _evaluate(selector) { 658 var clipRect = this.evaluate(function _evaluate(selector) {
611 return __utils__.getElementBounds(selector); 659 return window.__utils__.getElementBounds(selector);
612 }, { selector: selector }); 660 }, { selector: selector });
613 if (!utils.isClipRect(clipRect)) { 661 if (!utils.isClipRect(clipRect)) {
614 throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector); 662 throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector);
...@@ -623,13 +671,14 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) { ...@@ -623,13 +671,14 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) {
623 * @return mixed 671 * @return mixed
624 */ 672 */
625 Casper.prototype.getGlobal = function getGlobal(name) { 673 Casper.prototype.getGlobal = function getGlobal(name) {
674 "use strict";
626 var result = this.evaluate(function _evaluate(name) { 675 var result = this.evaluate(function _evaluate(name) {
627 var result = {}; 676 var result = {};
628 try { 677 try {
629 result.value = JSON.stringify(window[name]); 678 result.value = JSON.stringify(window[name]);
630 } catch (e) { 679 } catch (e) {
631 var message = f("Unable to JSON encode window.%s: %s", name, e); 680 var message = f("Unable to JSON encode window.%s: %s", name, e);
632 __utils__.log(message, "error"); 681 window.__utils__.log(message, "error");
633 result.error = message; 682 result.error = message;
634 } 683 }
635 return result; 684 return result;
...@@ -649,6 +698,7 @@ Casper.prototype.getGlobal = function getGlobal(name) { ...@@ -649,6 +698,7 @@ Casper.prototype.getGlobal = function getGlobal(name) {
649 * @return String 698 * @return String
650 */ 699 */
651 Casper.prototype.getTitle = function getTitle() { 700 Casper.prototype.getTitle = function getTitle() {
701 "use strict";
652 return this.evaluate(function _evaluate() { 702 return this.evaluate(function _evaluate() {
653 return document.title; 703 return document.title;
654 }); 704 });
...@@ -659,6 +709,7 @@ Casper.prototype.getTitle = function getTitle() { ...@@ -659,6 +709,7 @@ Casper.prototype.getTitle = function getTitle() {
659 * 709 *
660 */ 710 */
661 Casper.prototype.initErrorHandler = function initErrorHandler() { 711 Casper.prototype.initErrorHandler = function initErrorHandler() {
712 "use strict";
662 var casper = this; 713 var casper = this;
663 phantom.onError = function phantom_onError(msg, backtrace) { 714 phantom.onError = function phantom_onError(msg, backtrace) {
664 casper.emit('error', msg, backtrace); 715 casper.emit('error', msg, backtrace);
...@@ -673,8 +724,9 @@ Casper.prototype.initErrorHandler = function initErrorHandler() { ...@@ -673,8 +724,9 @@ Casper.prototype.initErrorHandler = function initErrorHandler() {
673 * 724 *
674 */ 725 */
675 Casper.prototype.injectClientUtils = function injectClientUtils() { 726 Casper.prototype.injectClientUtils = function injectClientUtils() {
727 "use strict";
676 var clientUtilsInjected = this.page.evaluate(function() { 728 var clientUtilsInjected = this.page.evaluate(function() {
677 return typeof __utils__ === "object"; 729 return typeof window.__utils__ === "object";
678 }); 730 });
679 if (true === clientUtilsInjected) { 731 if (true === clientUtilsInjected) {
680 return; 732 return;
...@@ -683,8 +735,13 @@ Casper.prototype.injectClientUtils = function injectClientUtils() { ...@@ -683,8 +735,13 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
683 if (true === this.page.injectJs(clientUtilsPath)) { 735 if (true === this.page.injectJs(clientUtilsPath)) {
684 this.log("Successfully injected Casper client-side utilities", "debug"); 736 this.log("Successfully injected Casper client-side utilities", "debug");
685 } else { 737 } else {
686 this.log("Failed to instantiate Casper client-side utilities!", "warning"); 738 this.warn("Failed to inject Casper client-side utilities");
687 } 739 }
740 // ClientUtils and Casper shares the same options
741 // These are not the lines I'm the most proud of in my life, but it works.
742 this.page.evaluate(function() {
743 window.__utils__ = new ClientUtils(__options);
744 }.toString().replace('__options', JSON.stringify(this.options)));
688 }; 745 };
689 746
690 /** 747 /**
...@@ -696,6 +753,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() { ...@@ -696,6 +753,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
696 * @return Casper 753 * @return Casper
697 */ 754 */
698 Casper.prototype.log = function log(message, level, space) { 755 Casper.prototype.log = function log(message, level, space) {
756 "use strict";
699 level = level && this.logLevels.indexOf(level) > -1 ? level : "debug"; 757 level = level && this.logLevels.indexOf(level) > -1 ? level : "debug";
700 space = space ? space : "phantom"; 758 space = space ? space : "phantom";
701 if (level === "error" && utils.isFunction(this.options.onError)) { 759 if (level === "error" && utils.isFunction(this.options.onError)) {
...@@ -735,12 +793,13 @@ Casper.prototype.log = function log(message, level, space) { ...@@ -735,12 +793,13 @@ Casper.prototype.log = function log(message, level, space) {
735 * @return Boolean 793 * @return Boolean
736 */ 794 */
737 Casper.prototype.mouseEvent = function mouseEvent(type, selector) { 795 Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
796 "use strict";
738 this.log("Mouse event '" + type + "' on selector: " + selector, "debug"); 797 this.log("Mouse event '" + type + "' on selector: " + selector, "debug");
739 if (!this.exists(selector)) { 798 if (!this.exists(selector)) {
740 throw new CasperError(f("Cannot dispatch %s event on nonexistent selector: %s", type, selector)); 799 throw new CasperError(f("Cannot dispatch %s event on nonexistent selector: %s", type, selector));
741 } 800 }
742 var eventSuccess = this.evaluate(function(type, selector) { 801 var eventSuccess = this.evaluate(function(type, selector) {
743 return __utils__.mouseEvent(type, selector); 802 return window.__utils__.mouseEvent(type, selector);
744 }, { 803 }, {
745 type: type, 804 type: type,
746 selector: selector 805 selector: selector
...@@ -758,13 +817,20 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) { ...@@ -758,13 +817,20 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
758 }; 817 };
759 818
760 /** 819 /**
761 * Performs an HTTP request. 820 * Performs an HTTP request, with optional settings.
821 *
822 * Available settings are:
823 *
824 * - String method: The HTTP method to use
825 * - Object data: The data to use to perform the request, eg. {foo: 'bar'}
826 * - Array headers: An array of request headers, eg. [{'Cache-Control': 'max-age=0'}]
762 * 827 *
763 * @param String location The url to open 828 * @param String location The url to open
764 * @param Object settings The request settings 829 * @param Object settings The request settings (optional)
765 * @return Casper 830 * @return Casper
766 */ 831 */
767 Casper.prototype.open = function open(location, settings) { 832 Casper.prototype.open = function open(location, settings) {
833 "use strict";
768 // settings validation 834 // settings validation
769 if (!settings) { 835 if (!settings) {
770 settings = { 836 settings = {
...@@ -805,9 +871,13 @@ Casper.prototype.open = function open(location, settings) { ...@@ -805,9 +871,13 @@ Casper.prototype.open = function open(location, settings) {
805 } 871 }
806 this.emit('open', this.requestUrl, settings); 872 this.emit('open', this.requestUrl, settings);
807 this.log(f('opening url: %s, HTTP %s', this.requestUrl, settings.method.toUpperCase()), "debug"); 873 this.log(f('opening url: %s, HTTP %s', this.requestUrl, settings.method.toUpperCase()), "debug");
874 if ('headers' in settings && phantom.version.minor < 6) {
875 this.warn('Custom headers in outgoing requests are supported in PhantomJS >= 1.6');
876 }
808 this.page.openUrl(this.requestUrl, { 877 this.page.openUrl(this.requestUrl, {
809 operation: settings.method, 878 operation: settings.method,
810 data: settings.data 879 data: settings.data,
880 headers: settings.headers
811 }, this.page.settings); 881 }, this.page.settings);
812 this.resources = []; 882 this.resources = [];
813 return this; 883 return this;
...@@ -822,6 +892,7 @@ Casper.prototype.open = function open(location, settings) { ...@@ -822,6 +892,7 @@ Casper.prototype.open = function open(location, settings) {
822 * @see Casper#then 892 * @see Casper#then
823 */ 893 */
824 Casper.prototype.repeat = function repeat(times, then) { 894 Casper.prototype.repeat = function repeat(times, then) {
895 "use strict";
825 for (var i = 0; i < times; i++) { 896 for (var i = 0; i < times; i++) {
826 this.then(then); 897 this.then(then);
827 } 898 }
...@@ -836,6 +907,7 @@ Casper.prototype.repeat = function repeat(times, then) { ...@@ -836,6 +907,7 @@ Casper.prototype.repeat = function repeat(times, then) {
836 * @return Boolean 907 * @return Boolean
837 */ 908 */
838 Casper.prototype.resourceExists = function resourceExists(test) { 909 Casper.prototype.resourceExists = function resourceExists(test) {
910 "use strict";
839 var testFn; 911 var testFn;
840 switch (utils.betterTypeOf(test)) { 912 switch (utils.betterTypeOf(test)) {
841 case "string": 913 case "string":
...@@ -866,6 +938,7 @@ Casper.prototype.resourceExists = function resourceExists(test) { ...@@ -866,6 +938,7 @@ Casper.prototype.resourceExists = function resourceExists(test) {
866 * @return Casper 938 * @return Casper
867 */ 939 */
868 Casper.prototype.run = function run(onComplete, time) { 940 Casper.prototype.run = function run(onComplete, time) {
941 "use strict";
869 if (!this.steps || this.steps.length < 1) { 942 if (!this.steps || this.steps.length < 1) {
870 this.log("No steps defined, aborting", "error"); 943 this.log("No steps defined, aborting", "error");
871 return this; 944 return this;
...@@ -882,6 +955,7 @@ Casper.prototype.run = function run(onComplete, time) { ...@@ -882,6 +955,7 @@ Casper.prototype.run = function run(onComplete, time) {
882 * @param Function step 955 * @param Function step
883 */ 956 */
884 Casper.prototype.runStep = function runStep(step) { 957 Casper.prototype.runStep = function runStep(step) {
958 "use strict";
885 var skipLog = utils.isObject(step.options) && step.options.skipLog === true; 959 var skipLog = utils.isObject(step.options) && step.options.skipLog === true;
886 var stepInfo = f("Step %d/%d", this.step, this.steps.length); 960 var stepInfo = f("Step %d/%d", this.step, this.steps.length);
887 var stepResult; 961 var stepResult;
...@@ -891,7 +965,7 @@ Casper.prototype.runStep = function runStep(step) { ...@@ -891,7 +965,7 @@ Casper.prototype.runStep = function runStep(step) {
891 if (utils.isNumber(this.options.stepTimeout) && this.options.stepTimeout > 0) { 965 if (utils.isNumber(this.options.stepTimeout) && this.options.stepTimeout > 0) {
892 var stepTimeoutCheckInterval = setInterval(function _check(self, start, stepNum) { 966 var stepTimeoutCheckInterval = setInterval(function _check(self, start, stepNum) {
893 if (new Date().getTime() - start > self.options.stepTimeout) { 967 if (new Date().getTime() - start > self.options.stepTimeout) {
894 if (self.step == stepNum) { 968 if (self.step === stepNum) {
895 self.emit('step.timeout'); 969 self.emit('step.timeout');
896 if (utils.isFunction(self.options.onStepTimeout)) { 970 if (utils.isFunction(self.options.onStepTimeout)) {
897 self.options.onStepTimeout.call(self, self); 971 self.options.onStepTimeout.call(self, self);
...@@ -922,6 +996,7 @@ Casper.prototype.runStep = function runStep(step) { ...@@ -922,6 +996,7 @@ Casper.prototype.runStep = function runStep(step) {
922 * @return Casper 996 * @return Casper
923 */ 997 */
924 Casper.prototype.setHttpAuth = function setHttpAuth(username, password) { 998 Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
999 "use strict";
925 if (!this.started) { 1000 if (!this.started) {
926 throw new CasperError("Casper must be started in order to use the setHttpAuth() method"); 1001 throw new CasperError("Casper must be started in order to use the setHttpAuth() method");
927 } 1002 }
...@@ -943,6 +1018,7 @@ Casper.prototype.setHttpAuth = function setHttpAuth(username, password) { ...@@ -943,6 +1018,7 @@ Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
943 * @return Casper 1018 * @return Casper
944 */ 1019 */
945 Casper.prototype.start = function start(location, then) { 1020 Casper.prototype.start = function start(location, then) {
1021 "use strict";
946 this.emit('starting'); 1022 this.emit('starting');
947 this.log('Starting...', "info"); 1023 this.log('Starting...', "info");
948 this.startTime = new Date().getTime(); 1024 this.startTime = new Date().getTime();
...@@ -997,6 +1073,7 @@ Casper.prototype.start = function start(location, then) { ...@@ -997,6 +1073,7 @@ Casper.prototype.start = function start(location, then) {
997 * @return Casper 1073 * @return Casper
998 */ 1074 */
999 Casper.prototype.then = function then(step) { 1075 Casper.prototype.then = function then(step) {
1076 "use strict";
1000 if (!this.started) { 1077 if (!this.started) {
1001 throw new CasperError("Casper not started; please use Casper#start"); 1078 throw new CasperError("Casper not started; please use Casper#start");
1002 } 1079 }
...@@ -1036,6 +1113,7 @@ Casper.prototype.then = function then(step) { ...@@ -1036,6 +1113,7 @@ Casper.prototype.then = function then(step) {
1036 * @see Casper#then 1113 * @see Casper#then
1037 */ 1114 */
1038 Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) { 1115 Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) {
1116 "use strict";
1039 if (arguments.length > 2) { 1117 if (arguments.length > 2) {
1040 this.emit("deprecated", "The thenClick() method does not process the fallbackToHref argument since 0.6"); 1118 this.emit("deprecated", "The thenClick() method does not process the fallbackToHref argument since 0.6");
1041 } 1119 }
...@@ -1055,6 +1133,7 @@ Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) ...@@ -1055,6 +1133,7 @@ Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref)
1055 * @see Casper#evaluate 1133 * @see Casper#evaluate
1056 */ 1134 */
1057 Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) { 1135 Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
1136 "use strict";
1058 return this.then(function _step() { 1137 return this.then(function _step() {
1059 this.evaluate(fn, context); 1138 this.evaluate(fn, context);
1060 }); 1139 });
...@@ -1069,6 +1148,7 @@ Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) { ...@@ -1069,6 +1148,7 @@ Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
1069 * @see Casper#open 1148 * @see Casper#open
1070 */ 1149 */
1071 Casper.prototype.thenOpen = function thenOpen(location, then) { 1150 Casper.prototype.thenOpen = function thenOpen(location, then) {
1151 "use strict";
1072 this.then(this.createStep(function _step() { 1152 this.then(this.createStep(function _step() {
1073 this.open(location); 1153 this.open(location);
1074 }, { 1154 }, {
...@@ -1089,10 +1169,26 @@ Casper.prototype.thenOpen = function thenOpen(location, then) { ...@@ -1089,10 +1169,26 @@ Casper.prototype.thenOpen = function thenOpen(location, then) {
1089 * @see Casper#open 1169 * @see Casper#open
1090 */ 1170 */
1091 Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn, context) { 1171 Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn, context) {
1172 "use strict";
1092 return this.thenOpen(location).thenEvaluate(fn, context); 1173 return this.thenOpen(location).thenEvaluate(fn, context);
1093 }; 1174 };
1094 1175
1095 /** 1176 /**
1177 * Sets the user-agent string currently used when requesting urls.
1178 *
1179 * @param String userAgent User agent string
1180 * @return String
1181 */
1182 Casper.prototype.userAgent = function userAgent(agent) {
1183 "use strict";
1184 if (!this.started) {
1185 throw new CasperError("Casper not started, can't set userAgent");
1186 }
1187 this.options.pageSettings.userAgent = this.page.settings.userAgent = agent;
1188 return this;
1189 };
1190
1191 /**
1096 * Changes the current viewport size. 1192 * Changes the current viewport size.
1097 * 1193 *
1098 * @param Number width The viewport width, in pixels 1194 * @param Number width The viewport width, in pixels
...@@ -1100,6 +1196,7 @@ Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn ...@@ -1100,6 +1196,7 @@ Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn
1100 * @return Casper 1196 * @return Casper
1101 */ 1197 */
1102 Casper.prototype.viewport = function viewport(width, height) { 1198 Casper.prototype.viewport = function viewport(width, height) {
1199 "use strict";
1103 if (!this.started) { 1200 if (!this.started) {
1104 throw new CasperError("Casper must be started in order to set viewport at runtime"); 1201 throw new CasperError("Casper must be started in order to set viewport at runtime");
1105 } 1202 }
...@@ -1123,12 +1220,26 @@ Casper.prototype.viewport = function viewport(width, height) { ...@@ -1123,12 +1220,26 @@ Casper.prototype.viewport = function viewport(width, height) {
1123 * @return Boolean 1220 * @return Boolean
1124 */ 1221 */
1125 Casper.prototype.visible = function visible(selector) { 1222 Casper.prototype.visible = function visible(selector) {
1223 "use strict";
1126 return this.evaluate(function _evaluate(selector) { 1224 return this.evaluate(function _evaluate(selector) {
1127 return __utils__.visible(selector); 1225 return window.__utils__.visible(selector);
1128 }, { selector: selector }); 1226 }, { selector: selector });
1129 }; 1227 };
1130 1228
1131 /** 1229 /**
1230 * Displays a warning message onto the console and logs the event.
1231 *
1232 * @param String message
1233 * @return Casper
1234 */
1235 Casper.prototype.warn = function warn(message) {
1236 "use strict";
1237 this.log(message, "warning", "phantom");
1238 var formatted = f.apply(null, ["⚠  " + message].concat([].slice.call(arguments, 1)));
1239 return this.echo(formatted, 'COMMENT');
1240 };
1241
1242 /**
1132 * Adds a new step that will wait for a given amount of time (expressed 1243 * Adds a new step that will wait for a given amount of time (expressed
1133 * in milliseconds) before processing an optional next one. 1244 * in milliseconds) before processing an optional next one.
1134 * 1245 *
...@@ -1137,6 +1248,7 @@ Casper.prototype.visible = function visible(selector) { ...@@ -1137,6 +1248,7 @@ Casper.prototype.visible = function visible(selector) {
1137 * @return Casper 1248 * @return Casper
1138 */ 1249 */
1139 Casper.prototype.wait = function wait(timeout, then) { 1250 Casper.prototype.wait = function wait(timeout, then) {
1251 "use strict";
1140 timeout = ~~timeout; 1252 timeout = ~~timeout;
1141 if (timeout < 1) { 1253 if (timeout < 1) {
1142 this.die("wait() only accepts a positive integer > 0 as a timeout value"); 1254 this.die("wait() only accepts a positive integer > 0 as a timeout value");
...@@ -1157,11 +1269,13 @@ Casper.prototype.wait = function wait(timeout, then) { ...@@ -1157,11 +1269,13 @@ Casper.prototype.wait = function wait(timeout, then) {
1157 }; 1269 };
1158 1270
1159 Casper.prototype.waitStart = function waitStart() { 1271 Casper.prototype.waitStart = function waitStart() {
1272 "use strict";
1160 this.emit('wait.start'); 1273 this.emit('wait.start');
1161 this.pendingWait = true; 1274 this.pendingWait = true;
1162 }; 1275 };
1163 1276
1164 Casper.prototype.waitDone = function waitDone() { 1277 Casper.prototype.waitDone = function waitDone() {
1278 "use strict";
1165 this.emit('wait.done'); 1279 this.emit('wait.done');
1166 this.pendingWait = false; 1280 this.pendingWait = false;
1167 }; 1281 };
...@@ -1176,6 +1290,7 @@ Casper.prototype.waitDone = function waitDone() { ...@@ -1176,6 +1290,7 @@ Casper.prototype.waitDone = function waitDone() {
1176 * @return Casper 1290 * @return Casper
1177 */ 1291 */
1178 Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { 1292 Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
1293 "use strict";
1179 timeout = timeout ? timeout : this.defaultWaitTimeout; 1294 timeout = timeout ? timeout : this.defaultWaitTimeout;
1180 if (!utils.isFunction(testFx)) { 1295 if (!utils.isFunction(testFx)) {
1181 this.die("waitFor() needs a test function"); 1296 this.die("waitFor() needs a test function");
...@@ -1223,6 +1338,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { ...@@ -1223,6 +1338,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
1223 * @return Casper 1338 * @return Casper
1224 */ 1339 */
1225 Casper.prototype.waitForResource = function waitForResource(test, then, onTimeout, timeout) { 1340 Casper.prototype.waitForResource = function waitForResource(test, then, onTimeout, timeout) {
1341 "use strict";
1226 timeout = timeout ? timeout : this.defaultWaitTimeout; 1342 timeout = timeout ? timeout : this.defaultWaitTimeout;
1227 return this.waitFor(function _check() { 1343 return this.waitFor(function _check() {
1228 return this.resourceExists(test); 1344 return this.resourceExists(test);
...@@ -1240,6 +1356,7 @@ Casper.prototype.waitForResource = function waitForResource(test, then, onTimeou ...@@ -1240,6 +1356,7 @@ Casper.prototype.waitForResource = function waitForResource(test, then, onTimeou
1240 * @return Casper 1356 * @return Casper
1241 */ 1357 */
1242 Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTimeout, timeout) { 1358 Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTimeout, timeout) {
1359 "use strict";
1243 timeout = timeout ? timeout : this.defaultWaitTimeout; 1360 timeout = timeout ? timeout : this.defaultWaitTimeout;
1244 return this.waitFor(function _check() { 1361 return this.waitFor(function _check() {
1245 return this.exists(selector); 1362 return this.exists(selector);
...@@ -1257,6 +1374,7 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi ...@@ -1257,6 +1374,7 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi
1257 * @return Casper 1374 * @return Casper
1258 */ 1375 */
1259 Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, onTimeout, timeout) { 1376 Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, onTimeout, timeout) {
1377 "use strict";
1260 timeout = timeout ? timeout : this.defaultWaitTimeout; 1378 timeout = timeout ? timeout : this.defaultWaitTimeout;
1261 return this.waitFor(function _check() { 1379 return this.waitFor(function _check() {
1262 return !this.exists(selector); 1380 return !this.exists(selector);
...@@ -1274,6 +1392,7 @@ Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, ...@@ -1274,6 +1392,7 @@ Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then,
1274 * @return Casper 1392 * @return Casper
1275 */ 1393 */
1276 Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, onTimeout, timeout) { 1394 Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, onTimeout, timeout) {
1395 "use strict";
1277 timeout = timeout ? timeout : this.defaultWaitTimeout; 1396 timeout = timeout ? timeout : this.defaultWaitTimeout;
1278 return this.waitFor(function _check() { 1397 return this.waitFor(function _check() {
1279 return this.visible(selector); 1398 return this.visible(selector);
...@@ -1291,6 +1410,7 @@ Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, on ...@@ -1291,6 +1410,7 @@ Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, on
1291 * @return Casper 1410 * @return Casper
1292 */ 1411 */
1293 Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, onTimeout, timeout) { 1412 Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, onTimeout, timeout) {
1413 "use strict";
1294 timeout = timeout ? timeout : this.defaultWaitTimeout; 1414 timeout = timeout ? timeout : this.defaultWaitTimeout;
1295 return this.waitFor(function _check() { 1415 return this.waitFor(function _check() {
1296 return !this.visible(selector); 1416 return !this.visible(selector);
...@@ -1305,7 +1425,8 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on ...@@ -1305,7 +1425,8 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
1305 * @since 0.6 1425 * @since 0.6
1306 */ 1426 */
1307 Casper.extend = function(proto) { 1427 Casper.extend = function(proto) {
1308 console.warn('Casper.extend() has been deprecated since 0.6; check the docs'); 1428 "use strict";
1429 this.warn('Casper.extend() has been deprecated since 0.6; check the docs');
1309 if (!utils.isObject(proto)) { 1430 if (!utils.isObject(proto)) {
1310 throw new CasperError("extends() only accept objects as prototypes"); 1431 throw new CasperError("extends() only accept objects as prototypes");
1311 } 1432 }
...@@ -1321,12 +1442,8 @@ exports.Casper = Casper; ...@@ -1321,12 +1442,8 @@ exports.Casper = Casper;
1321 * @return WebPage 1442 * @return WebPage
1322 */ 1443 */
1323 function createPage(casper) { 1444 function createPage(casper) {
1324 var page; 1445 "use strict";
1325 if (phantom.version.major <= 1 && phantom.version.minor < 3 && utils.isFunction(require)) { 1446 var page = require('webpage').create();
1326 page = new WebPage();
1327 } else {
1328 page = require('webpage').create();
1329 }
1330 page.onAlert = function onAlert(message) { 1447 page.onAlert = function onAlert(message) {
1331 casper.log('[alert] ' + message, "info", "remote"); 1448 casper.log('[alert] ' + message, "info", "remote");
1332 casper.emit('remote.alert', message); 1449 casper.emit('remote.alert', message);
...@@ -1375,17 +1492,19 @@ function createPage(casper) { ...@@ -1375,17 +1492,19 @@ function createPage(casper) {
1375 } 1492 }
1376 } 1493 }
1377 if (casper.options.clientScripts) { 1494 if (casper.options.clientScripts) {
1495 if (utils.isString(casper.options.clientScripts)) {
1496 casper.options.clientScripts = [casper.options.clientScripts];
1497 }
1378 if (!utils.isArray(casper.options.clientScripts)) { 1498 if (!utils.isArray(casper.options.clientScripts)) {
1379 throw new CasperError("The clientScripts option must be an array"); 1499 throw new CasperError("The clientScripts option must be an array");
1380 } else {
1381 casper.options.clientScripts.forEach(function _forEach(script) {
1382 if (casper.page.injectJs(script)) {
1383 casper.log(f('Automatically injected %s client side', script), "debug");
1384 } else {
1385 casper.log(f('Failed injecting %s client side', script), "warning");
1386 }
1387 });
1388 } 1500 }
1501 casper.options.clientScripts.forEach(function _forEach(script) {
1502 if (casper.page.injectJs(script)) {
1503 casper.log(f('Automatically injected %s client side', script), "debug");
1504 } else {
1505 casper.warn('Failed injecting %s client side', script);
1506 }
1507 });
1389 } 1508 }
1390 // Client-side utils injection 1509 // Client-side utils injection
1391 casper.injectClientUtils(); 1510 casper.injectClientUtils();
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
28 * 28 *
29 */ 29 */
30 30
31 /*global CasperError console exports phantom require*/
32
31 var utils = require('utils'); 33 var utils = require('utils');
32 34
33 /** 35 /**
...@@ -38,6 +40,7 @@ var utils = require('utils'); ...@@ -38,6 +40,7 @@ var utils = require('utils');
38 * @return Object 40 * @return Object
39 */ 41 */
40 exports.parse = function parse(phantomArgs) { 42 exports.parse = function parse(phantomArgs) {
43 "use strict";
41 var extract = { 44 var extract = {
42 args: [], 45 args: [],
43 options: {}, 46 options: {},
...@@ -110,6 +113,7 @@ exports.parse = function parse(phantomArgs) { ...@@ -110,6 +113,7 @@ exports.parse = function parse(phantomArgs) {
110 * @return Mixed 113 * @return Mixed
111 */ 114 */
112 function castArgument(arg) { 115 function castArgument(arg) {
116 "use strict";
113 if (arg.match(/^-?\d+$/)) { 117 if (arg.match(/^-?\d+$/)) {
114 return parseInt(arg, 10); 118 return parseInt(arg, 10);
115 } else if (arg.match(/^-?\d+\.\d+$/)) { 119 } else if (arg.match(/^-?\d+\.\d+$/)) {
......
...@@ -27,15 +27,21 @@ ...@@ -27,15 +27,21 @@
27 * DEALINGS IN THE SOFTWARE. 27 * DEALINGS IN THE SOFTWARE.
28 * 28 *
29 */ 29 */
30
31 /*global console escape exports NodeList window*/
32
30 (function(exports) { 33 (function(exports) {
31 exports.create = function create() { 34 "use strict";
32 return new this.ClientUtils(); 35
36 exports.create = function create(options) {
37 return new this.ClientUtils(options);
33 }; 38 };
34 39
35 /** 40 /**
36 * Casper client-side helpers. 41 * Casper client-side helpers.
37 */ 42 */
38 exports.ClientUtils = function ClientUtils() { 43 exports.ClientUtils = function ClientUtils(options) {
44 // private members
39 var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 45 var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
40 var BASE64_DECODE_CHARS = new Array( 46 var BASE64_DECODE_CHARS = new Array(
41 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 47 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
...@@ -49,6 +55,9 @@ ...@@ -49,6 +55,9 @@
49 ); 55 );
50 var SUPPORTED_SELECTOR_TYPES = ['css', 'xpath']; 56 var SUPPORTED_SELECTOR_TYPES = ['css', 'xpath'];
51 57
58 // public members
59 this.options = options || {};
60
52 /** 61 /**
53 * Clicks on the DOM element behind the provided selector. 62 * Clicks on the DOM element behind the provided selector.
54 * 63 *
...@@ -70,35 +79,35 @@ ...@@ -70,35 +79,35 @@
70 while (i < len) { 79 while (i < len) {
71 do { 80 do {
72 c1 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff]; 81 c1 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
73 } while (i < len && c1 == -1); 82 } while (i < len && c1 === -1);
74 if (c1 == -1) { 83 if (c1 === -1) {
75 break; 84 break;
76 } 85 }
77 do { 86 do {
78 c2 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff]; 87 c2 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
79 } while (i < len && c2 == -1); 88 } while (i < len && c2 === -1);
80 if (c2 == -1) { 89 if (c2 === -1) {
81 break; 90 break;
82 } 91 }
83 out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); 92 out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
84 do { 93 do {
85 c3 = str.charCodeAt(i++) & 0xff; 94 c3 = str.charCodeAt(i++) & 0xff;
86 if (c3 == 61) 95 if (c3 === 61)
87 return out; 96 return out;
88 c3 = BASE64_DECODE_CHARS[c3]; 97 c3 = BASE64_DECODE_CHARS[c3];
89 } while (i < len && c3 == -1); 98 } while (i < len && c3 === -1);
90 if (c3 == -1) { 99 if (c3 === -1) {
91 break; 100 break;
92 } 101 }
93 out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); 102 out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
94 do { 103 do {
95 c4 = str.charCodeAt(i++) & 0xff; 104 c4 = str.charCodeAt(i++) & 0xff;
96 if (c4 == 61) { 105 if (c4 === 61) {
97 return out; 106 return out;
98 } 107 }
99 c4 = BASE64_DECODE_CHARS[c4]; 108 c4 = BASE64_DECODE_CHARS[c4];
100 } while (i < len && c4 == -1); 109 } while (i < len && c4 === -1);
101 if (c4 == -1) { 110 if (c4 === -1) {
102 break; 111 break;
103 } 112 }
104 out += String.fromCharCode(((c3 & 0x03) << 6) | c4); 113 out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
...@@ -117,14 +126,14 @@ ...@@ -117,14 +126,14 @@
117 var out = "", i = 0, len = str.length, c1, c2, c3; 126 var out = "", i = 0, len = str.length, c1, c2, c3;
118 while (i < len) { 127 while (i < len) {
119 c1 = str.charCodeAt(i++) & 0xff; 128 c1 = str.charCodeAt(i++) & 0xff;
120 if (i == len) { 129 if (i === len) {
121 out += BASE64_ENCODE_CHARS.charAt(c1 >> 2); 130 out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
122 out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4); 131 out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4);
123 out += "=="; 132 out += "==";
124 break; 133 break;
125 } 134 }
126 c2 = str.charCodeAt(i++); 135 c2 = str.charCodeAt(i++);
127 if (i == len) { 136 if (i === len) {
128 out += BASE64_ENCODE_CHARS.charAt(c1 >> 2); 137 out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
129 out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); 138 out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
130 out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2); 139 out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2);
...@@ -165,7 +174,7 @@ ...@@ -165,7 +174,7 @@
165 var text = '', elements = this.findAll(selector); 174 var text = '', elements = this.findAll(selector);
166 if (elements && elements.length) { 175 if (elements && elements.length) {
167 Array.prototype.forEach.call(elements, function _forEach(element) { 176 Array.prototype.forEach.call(elements, function _forEach(element) {
168 text += element.innerText; 177 text += element.textContent || element.innerText;
169 }); 178 });
170 } 179 }
171 return text; 180 return text;
...@@ -185,7 +194,7 @@ ...@@ -185,7 +194,7 @@
185 files: [] 194 files: []
186 }; 195 };
187 if (!(form instanceof HTMLElement) || typeof form === "string") { 196 if (!(form instanceof HTMLElement) || typeof form === "string") {
188 __utils__.log("attempting to fetch form element from selector: '" + form + "'", "info"); 197 this.log("attempting to fetch form element from selector: '" + form + "'", "info");
189 try { 198 try {
190 form = this.findOne(form); 199 form = this.findOne(form);
191 } catch (e) { 200 } catch (e) {
...@@ -203,7 +212,7 @@ ...@@ -203,7 +212,7 @@
203 if (!vals.hasOwnProperty(name)) { 212 if (!vals.hasOwnProperty(name)) {
204 continue; 213 continue;
205 } 214 }
206 var field = this.findAll('[name="' + name + '"]'); 215 var field = this.findAll('[name="' + name + '"]', form);
207 var value = vals[name]; 216 var value = vals[name];
208 if (!field) { 217 if (!field) {
209 out.errors.push('no field named "' + name + '" in form'); 218 out.errors.push('no field named "' + name + '" in form');
...@@ -229,16 +238,18 @@ ...@@ -229,16 +238,18 @@
229 /** 238 /**
230 * Finds all DOM elements matching by the provided selector. 239 * Finds all DOM elements matching by the provided selector.
231 * 240 *
232 * @param String selector CSS3 selector 241 * @param String selector CSS3 selector
242 * @param HTMLElement|null scope Element to search child elements within
233 * @return NodeList|undefined 243 * @return NodeList|undefined
234 */ 244 */
235 this.findAll = function findAll(selector) { 245 this.findAll = function findAll(selector, scope) {
246 scope = scope || document;
236 try { 247 try {
237 var pSelector = this.processSelector(selector); 248 var pSelector = this.processSelector(selector);
238 if (pSelector.type === 'xpath') { 249 if (pSelector.type === 'xpath') {
239 return this.getElementsByXPath(pSelector.path); 250 return this.getElementsByXPath(pSelector.path);
240 } else { 251 } else {
241 return document.querySelectorAll(pSelector.path); 252 return scope.querySelectorAll(pSelector.path);
242 } 253 }
243 } catch (e) { 254 } catch (e) {
244 this.log('findAll(): invalid selector provided "' + selector + '":' + e, "error"); 255 this.log('findAll(): invalid selector provided "' + selector + '":' + e, "error");
...@@ -248,16 +259,18 @@ ...@@ -248,16 +259,18 @@
248 /** 259 /**
249 * Finds a DOM element by the provided selector. 260 * Finds a DOM element by the provided selector.
250 * 261 *
251 * @param String selector CSS3 selector 262 * @param String selector CSS3 selector
263 * @param HTMLElement|null scope Element to search child elements within
252 * @return HTMLElement|undefined 264 * @return HTMLElement|undefined
253 */ 265 */
254 this.findOne = function findOne(selector) { 266 this.findOne = function findOne(selector, scope) {
267 scope = scope || document;
255 try { 268 try {
256 var pSelector = this.processSelector(selector); 269 var pSelector = this.processSelector(selector);
257 if (pSelector.type === 'xpath') { 270 if (pSelector.type === 'xpath') {
258 return this.getElementByXPath(pSelector.path); 271 return this.getElementByXPath(pSelector.path);
259 } else { 272 } else {
260 return document.querySelector(pSelector.path); 273 return scope.querySelector(pSelector.path);
261 } 274 }
262 } catch (e) { 275 } catch (e) {
263 this.log('findOne(): invalid selector provided "' + selector + '":' + e, "error"); 276 this.log('findOne(): invalid selector provided "' + selector + '":' + e, "error");
...@@ -462,8 +475,8 @@ ...@@ -462,8 +475,8 @@
462 * @param mixed value The field value to set 475 * @param mixed value The field value to set
463 */ 476 */
464 this.setField = function setField(field, value) { 477 this.setField = function setField(field, value) {
465 var fields, out; 478 var logValue, fields, out;
466 value = value || ""; 479 value = logValue = (value || "");
467 if (field instanceof NodeList) { 480 if (field instanceof NodeList) {
468 fields = field; 481 fields = field;
469 field = fields[0]; 482 field = fields[0];
...@@ -471,11 +484,15 @@ ...@@ -471,11 +484,15 @@
471 if (!field instanceof HTMLElement) { 484 if (!field instanceof HTMLElement) {
472 this.log("Invalid field type; only HTMLElement and NodeList are supported", "error"); 485 this.log("Invalid field type; only HTMLElement and NodeList are supported", "error");
473 } 486 }
474 this.log('Set "' + field.getAttribute('name') + '" field value to ' + value, "debug"); 487 if (this.options && this.options.safeLogs && field.getAttribute('type') === "password") {
488 // obfuscate password value
489 logValue = new Array(value.length + 1).join("*");
490 }
491 this.log('Set "' + field.getAttribute('name') + '" field value to ' + logValue, "debug");
475 try { 492 try {
476 field.focus(); 493 field.focus();
477 } catch (e) { 494 } catch (e) {
478 __utils__.log("Unable to focus() input field " + field.getAttribute('name') + ": " + e, "warning"); 495 this.log("Unable to focus() input field " + field.getAttribute('name') + ": " + e, "warning");
479 } 496 }
480 var nodeName = field.nodeName.toLowerCase(); 497 var nodeName = field.nodeName.toLowerCase();
481 switch (nodeName) { 498 switch (nodeName) {
...@@ -544,7 +561,7 @@ ...@@ -544,7 +561,7 @@
544 try { 561 try {
545 field.blur(); 562 field.blur();
546 } catch (err) { 563 } catch (err) {
547 __utils__.log("Unable to blur() input field " + field.getAttribute('name') + ": " + err, "warning"); 564 this.log("Unable to blur() input field " + field.getAttribute('name') + ": " + err, "warning");
548 } 565 }
549 return out; 566 return out;
550 }; 567 };
...@@ -570,7 +587,4 @@ ...@@ -570,7 +587,4 @@
570 } 587 }
571 }; 588 };
572 }; 589 };
573
574 // silly "hack" to force having an instance available
575 exports.__utils__ = new exports.ClientUtils();
576 })(typeof exports === "object" ? exports : window); 590 })(typeof exports === "object" ? exports : window);
......
...@@ -28,10 +28,13 @@ ...@@ -28,10 +28,13 @@
28 * 28 *
29 */ 29 */
30 30
31 /*global exports console require*/
32
31 var fs = require('fs'); 33 var fs = require('fs');
32 var utils = require('utils'); 34 var utils = require('utils');
33 35
34 exports.create = function create(type) { 36 exports.create = function create(type) {
37 "use strict";
35 if (!type) { 38 if (!type) {
36 return; 39 return;
37 } 40 }
...@@ -48,6 +51,7 @@ exports.create = function create(type) { ...@@ -48,6 +51,7 @@ exports.create = function create(type) {
48 * (c) Fabien Potencier, Symfony project, MIT license 51 * (c) Fabien Potencier, Symfony project, MIT license
49 */ 52 */
50 var Colorizer = function Colorizer() { 53 var Colorizer = function Colorizer() {
54 "use strict";
51 var options = { bold: 1, underscore: 4, blink: 5, reverse: 7, conceal: 8 }; 55 var options = { bold: 1, underscore: 4, blink: 5, reverse: 7, conceal: 8 };
52 var foreground = { black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37 }; 56 var foreground = { black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37 };
53 var background = { black: 40, red: 41, green: 42, yellow: 43, blue: 44, magenta: 45, cyan: 46, white: 47 }; 57 var background = { black: 40, red: 41, green: 42, yellow: 43, blue: 44, magenta: 45, cyan: 46, white: 47 };
...@@ -104,7 +108,7 @@ var Colorizer = function Colorizer() { ...@@ -104,7 +108,7 @@ var Colorizer = function Colorizer() {
104 if (typeof pad === "number" && text.length < pad) { 108 if (typeof pad === "number" && text.length < pad) {
105 text += new Array(pad - text.length + 1).join(' '); 109 text += new Array(pad - text.length + 1).join(' ');
106 } 110 }
107 return "\033[" + codes.join(';') + 'm' + text + "\033[0m"; 111 return "\u001b[" + codes.join(';') + 'm' + text + "\u001b[0m";
108 }; 112 };
109 }; 113 };
110 exports.Colorizer = Colorizer; 114 exports.Colorizer = Colorizer;
...@@ -114,6 +118,7 @@ exports.Colorizer = Colorizer; ...@@ -114,6 +118,7 @@ exports.Colorizer = Colorizer;
114 * 118 *
115 */ 119 */
116 var Dummy = function Dummy() { 120 var Dummy = function Dummy() {
121 "use strict";
117 this.colorize = function colorize(text, styleName, pad) { 122 this.colorize = function colorize(text, styleName, pad) {
118 return text; 123 return text;
119 }; 124 };
......
...@@ -28,9 +28,12 @@ ...@@ -28,9 +28,12 @@
28 * 28 *
29 */ 29 */
30 30
31 /*global CasperError console encodeURIComponent escape exports require*/
32
31 var utils = require('utils'); 33 var utils = require('utils');
32 34
33 exports.create = function create(fn) { 35 exports.create = function create(fn) {
36 "use strict";
34 return new FunctionArgsInjector(fn); 37 return new FunctionArgsInjector(fn);
35 }; 38 };
36 39
...@@ -40,6 +43,7 @@ exports.create = function create(fn) { ...@@ -40,6 +43,7 @@ exports.create = function create(fn) {
40 * FIXME: use new Function() instead of eval() 43 * FIXME: use new Function() instead of eval()
41 */ 44 */
42 var FunctionArgsInjector = function FunctionArgsInjector(fn) { 45 var FunctionArgsInjector = function FunctionArgsInjector(fn) {
46 "use strict";
43 if (!utils.isFunction(fn)) { 47 if (!utils.isFunction(fn)) {
44 throw new CasperError("FunctionArgsInjector() can only process functions"); 48 throw new CasperError("FunctionArgsInjector() can only process functions");
45 } 49 }
......
...@@ -28,13 +28,17 @@ ...@@ -28,13 +28,17 @@
28 * 28 *
29 */ 29 */
30 30
31 /*global CasperError exports require*/
32
31 var utils = require('utils'); 33 var utils = require('utils');
32 34
33 exports.create = function create(casper) { 35 exports.create = function create(casper) {
36 "use strict";
34 return new Mouse(casper); 37 return new Mouse(casper);
35 }; 38 };
36 39
37 var Mouse = function Mouse(casper) { 40 var Mouse = function Mouse(casper) {
41 "use strict";
38 if (!utils.isCasperObject(casper)) { 42 if (!utils.isCasperObject(casper)) {
39 throw new CasperError('Mouse() needs a Casper instance'); 43 throw new CasperError('Mouse() needs a Casper instance');
40 } 44 }
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
28 * 28 *
29 */ 29 */
30 30
31 /*global exports:false, phantom:false, require:false, CasperError:false*/ 31 /*global CasperError exports phantom require*/
32 32
33 var fs = require('fs'); 33 var fs = require('fs');
34 var events = require('events'); 34 var events = require('events');
...@@ -55,6 +55,7 @@ var Tester = function Tester(casper, options) { ...@@ -55,6 +55,7 @@ var Tester = function Tester(casper, options) {
55 55
56 this.currentTestFile = null; 56 this.currentTestFile = null;
57 this.exporter = require('xunit').create(); 57 this.exporter = require('xunit').create();
58 this.includes = [];
58 this.running = false; 59 this.running = false;
59 this.suites = []; 60 this.suites = [];
60 this.options = utils.mergeObjects({ 61 this.options = utils.mergeObjects({
...@@ -287,7 +288,7 @@ var Tester = function Tester(casper, options) { ...@@ -287,7 +288,7 @@ var Tester = function Tester(casper, options) {
287 standard: "Subject matches the provided pattern", 288 standard: "Subject matches the provided pattern",
288 values: { 289 values: {
289 subject: subject, 290 subject: subject,
290 pattern: pattern 291 pattern: pattern.toString()
291 } 292 }
292 }); 293 });
293 }; 294 };
...@@ -361,7 +362,7 @@ var Tester = function Tester(casper, options) { ...@@ -361,7 +362,7 @@ var Tester = function Tester(casper, options) {
361 */ 362 */
362 this.assertTextExists = this.assertTextExist = function assertTextExists(text, message) { 363 this.assertTextExists = this.assertTextExist = function assertTextExists(text, message) {
363 var textFound = (casper.evaluate(function _evaluate() { 364 var textFound = (casper.evaluate(function _evaluate() {
364 return document.body.innerText; 365 return document.body.textContent || document.body.innerText;
365 }).indexOf(text) !== -1); 366 }).indexOf(text) !== -1);
366 return this.assert(textFound, message, { 367 return this.assert(textFound, message, {
367 type: "assertTextExists", 368 type: "assertTextExists",
...@@ -392,6 +393,25 @@ var Tester = function Tester(casper, options) { ...@@ -392,6 +393,25 @@ var Tester = function Tester(casper, options) {
392 }; 393 };
393 394
394 /** 395 /**
396 * Asserts that title of the remote page matched the provided pattern.
397 *
398 * @param RegExp pattern The pattern to test the title against
399 * @param String message Test description
400 * @return Object An assertion result object
401 */
402 this.assertTitleMatch = this.assertTitleMatches = function assertTitleMatch(pattern, message) {
403 var currentTitle = casper.getTitle();
404 return this.assert(pattern.test(currentTitle), message, {
405 type: "assertTitle",
406 details: "Page title does not match the provided pattern",
407 values: {
408 subject: currentTitle,
409 pattern: pattern.toString()
410 }
411 });
412 };
413
414 /**
395 * Asserts that the provided subject is of the given type. 415 * Asserts that the provided subject is of the given type.
396 * 416 *
397 * @param mixed subject The value to test 417 * @param mixed subject The value to test
...@@ -427,7 +447,7 @@ var Tester = function Tester(casper, options) { ...@@ -427,7 +447,7 @@ var Tester = function Tester(casper, options) {
427 standard: "Current url matches the provided pattern", 447 standard: "Current url matches the provided pattern",
428 values: { 448 values: {
429 currentUrl: currentUrl, 449 currentUrl: currentUrl,
430 pattern: pattern 450 pattern: pattern.toString()
431 } 451 }
432 }); 452 });
433 }; 453 };
...@@ -695,6 +715,9 @@ var Tester = function Tester(casper, options) { ...@@ -695,6 +715,9 @@ var Tester = function Tester(casper, options) {
695 this.runTest = function runTest(testFile) { 715 this.runTest = function runTest(testFile) {
696 this.bar(f('Test file: %s', testFile), 'INFO_BAR'); 716 this.bar(f('Test file: %s', testFile), 'INFO_BAR');
697 this.running = true; // this.running is set back to false with done() 717 this.running = true; // this.running is set back to false with done()
718 this.includes.forEach(function(include) {
719 phantom.injectJs(include);
720 });
698 this.exec(testFile); 721 this.exec(testFile);
699 }; 722 };
700 723
......
...@@ -28,329 +28,343 @@ ...@@ -28,329 +28,343 @@
28 * 28 *
29 */ 29 */
30 30
31 /** 31 /*global CasperError console exports phantom require*/
32 * Provides a better typeof operator equivalent, able to retrieve the array
33 * type.
34 *
35 * @param mixed input
36 * @return String
37 * @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
38 */
39 function betterTypeOf(input) {
40 try {
41 return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
42 } catch (e) {
43 return typeof input;
44 }
45 }
46 exports.betterTypeOf = betterTypeOf;
47 32
48 /** 33 (function(exports) {
49 * Dumps a JSON representation of passed value to the console. Used for 34 "use strict";
50 * debugging purpose only.
51 *
52 * @param Mixed value
53 */
54 function dump(value) {
55 console.log(serialize(value));
56 }
57 exports.dump = dump;
58 35
59 /** 36 /**
60 * Returns the file extension in lower case. 37 * Provides a better typeof operator equivalent, able to retrieve the array
61 * 38 * type.
62 * @param String file File path 39 *
63 * @return string 40 * @param mixed input
64 */ 41 * @return String
65 function fileExt(file) { 42 * @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
66 try { 43 */
67 return file.split('.').pop().toLowerCase().trim(); 44 function betterTypeOf(input) {
68 } catch(e) { 45 try {
69 return ''; 46 return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
47 } catch (e) {
48 return typeof input;
49 }
70 } 50 }
71 } 51 exports.betterTypeOf = betterTypeOf;
72 exports.fileExt = fileExt;
73 52
74 /** 53 /**
75 * Takes a string and append blanks until the pad value is reached. 54 * Dumps a JSON representation of passed value to the console. Used for
76 * 55 * debugging purpose only.
77 * @param String text 56 *
78 * @param Number pad Pad value (optional; default: 80) 57 * @param Mixed value
79 * @return String 58 */
80 */ 59 function dump(value) {
81 function fillBlanks(text, pad) { 60 console.log(serialize(value));
82 pad = pad || 80;
83 if (text.length < pad) {
84 text += new Array(pad - text.length + 1).join(' ');
85 } 61 }
86 return text; 62 exports.dump = dump;
87 }
88 exports.fillBlanks = fillBlanks;
89 63
90 /** 64 /**
91 * Formats a string with passed parameters. Ported from nodejs `util.format()`. 65 * Returns the file extension in lower case.
92 * 66 *
93 * @return String 67 * @param String file File path
94 */ 68 * @return string
95 function format(f) { 69 */
96 var i; 70 function fileExt(file) {
97 if (typeof f !== 'string') { 71 try {
98 var objects = []; 72 return file.split('.').pop().toLowerCase().trim();
99 for (i = 0; i < arguments.length; i++) { 73 } catch(e) {
100 objects.push(inspect(arguments[i])); 74 return '';
101 } 75 }
102 return objects.join(' ');
103 } 76 }
104 i = 1; 77 exports.fileExt = fileExt;
105 var args = arguments; 78
106 var len = args.length; 79 /**
107 var str = String(f).replace(/%[sdj%]/g, function _replace(x) { 80 * Takes a string and append blanks until the pad value is reached.
108 if (i >= len) return x; 81 *
109 switch (x) { 82 * @param String text
110 case '%s': 83 * @param Number pad Pad value (optional; default: 80)
111 return String(args[i++]); 84 * @return String
112 case '%d': 85 */
113 return Number(args[i++]); 86 function fillBlanks(text, pad) {
114 case '%j': 87 pad = pad || 80;
115 return JSON.stringify(args[i++]); 88 if (text.length < pad) {
116 case '%%': 89 text += new Array(pad - text.length + 1).join(' ');
117 return '%';
118 default:
119 return x;
120 }
121 });
122 for (var x = args[i]; i < len; x = args[++i]) {
123 if (x === null || typeof x !== 'object') {
124 str += ' ' + x;
125 } else {
126 str += ' ' + inspect(x);
127 } 90 }
91 return text;
128 } 92 }
129 return str; 93 exports.fillBlanks = fillBlanks;
130 }
131 exports.format = format;
132 94
133 /** 95 /**
134 * Inherit the prototype methods from one constructor into another. 96 * Formats a string with passed parameters. Ported from nodejs `util.format()`.
135 * 97 *
136 * @param {function} ctor Constructor function which needs to inherit the 98 * @return String
137 * prototype. 99 */
138 * @param {function} superCtor Constructor function to inherit prototype from. 100 function format(f) {
139 */ 101 var i = 1;
140 function inherits(ctor, superCtor) { 102 var args = arguments;
141 ctor.super_ = ctor.__super__ = superCtor; 103 var len = args.length;
142 ctor.prototype = Object.create(superCtor.prototype, { 104 var str = String(f).replace(/%[sdj%]/g, function _replace(x) {
143 constructor: { 105 if (i >= len) return x;
144 value: ctor, 106 switch (x) {
145 enumerable: false, 107 case '%s':
146 writable: true, 108 return String(args[i++]);
147 configurable: true 109 case '%d':
110 return Number(args[i++]);
111 case '%j':
112 return JSON.stringify(args[i++]);
113 case '%%':
114 return '%';
115 default:
116 return x;
117 }
118 });
119 for (var x = args[i]; i < len; x = args[++i]) {
120 if (x === null || typeof x !== 'object') {
121 str += ' ' + x;
122 } else {
123 str += '[obj]';
124 }
148 } 125 }
149 }); 126 return str;
150 } 127 }
151 exports.inherits = inherits; 128 exports.format = format;
152 129
153 /** 130 /**
154 * Checks if value is a javascript Array 131 * Inherit the prototype methods from one constructor into another.
155 * 132 *
156 * @param mixed value 133 * @param {function} ctor Constructor function which needs to inherit the
157 * @return Boolean 134 * prototype.
158 */ 135 * @param {function} superCtor Constructor function to inherit prototype from.
159 function isArray(value) { 136 */
160 return Array.isArray(value) || isType(value, "array"); 137 function inherits(ctor, superCtor) {
161 } 138 ctor.super_ = ctor.__super__ = superCtor;
162 exports.isArray = isArray; 139 ctor.prototype = Object.create(superCtor.prototype, {
140 constructor: {
141 value: ctor,
142 enumerable: false,
143 writable: true,
144 configurable: true
145 }
146 });
147 }
148 exports.inherits = inherits;
163 149
164 /** 150 /**
165 * Checks if passed argument is an instance of Capser object. 151 * Checks if value is a javascript Array
166 * 152 *
167 * @param mixed value 153 * @param mixed value
168 * @return Boolean 154 * @return Boolean
169 */ 155 */
170 function isCasperObject(value) { 156 function isArray(value) {
171 return value instanceof require('casper').Casper; 157 return Array.isArray(value) || isType(value, "array");
172 } 158 }
173 exports.isCasperObject = isCasperObject; 159 exports.isArray = isArray;
174 160
175 /** 161 /**
176 * Checks if value is a phantomjs clipRect-compatible object 162 * Checks if passed argument is an instance of Capser object.
177 * 163 *
178 * @param mixed value 164 * @param mixed value
179 * @return Boolean 165 * @return Boolean
180 */ 166 */
181 function isClipRect(value) { 167 function isCasperObject(value) {
182 return isType(value, "cliprect") || ( 168 return value instanceof require('casper').Casper;
183 isObject(value) && 169 }
184 isNumber(value.top) && isNumber(value.left) && 170 exports.isCasperObject = isCasperObject;
185 isNumber(value.width) && isNumber(value.height)
186 );
187 }
188 exports.isClipRect = isClipRect;
189 171
190 /** 172 /**
191 * Checks if value is a javascript Function 173 * Checks if value is a phantomjs clipRect-compatible object
192 * 174 *
193 * @param mixed value 175 * @param mixed value
194 * @return Boolean 176 * @return Boolean
195 */ 177 */
196 function isFunction(value) { 178 function isClipRect(value) {
197 return isType(value, "function"); 179 return isType(value, "cliprect") || (
198 } 180 isObject(value) &&
199 exports.isFunction = isFunction; 181 isNumber(value.top) && isNumber(value.left) &&
182 isNumber(value.width) && isNumber(value.height)
183 );
184 }
185 exports.isClipRect = isClipRect;
200 186
201 /** 187 /**
202 * Checks if a file is apparently javascript compatible (.js or .coffee). 188 * Checks if value is a javascript Function
203 * 189 *
204 * @param String file Path to the file to test 190 * @param mixed value
205 * @return Boolean 191 * @return Boolean
206 */ 192 */
207 function isJsFile(file) { 193 function isFunction(value) {
208 var ext = fileExt(file); 194 return isType(value, "function");
209 return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1; 195 }
210 } 196 exports.isFunction = isFunction;
211 exports.isJsFile = isJsFile;
212 197
213 /** 198 /**
214 * Checks if the provided value is null 199 * Checks if a file is apparently javascript compatible (.js or .coffee).
215 * 200 *
216 * @return Boolean 201 * @param String file Path to the file to test
217 */ 202 * @return Boolean
218 function isNull(value) { 203 */
219 return isType(value, "null"); 204 function isJsFile(file) {
220 } 205 var ext = fileExt(file);
221 exports.isNull = isNull; 206 return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1;
207 }
208 exports.isJsFile = isJsFile;
222 209
223 /** 210 /**
224 * Checks if value is a javascript Number 211 * Checks if the provided value is null
225 * 212 *
226 * @param mixed value 213 * @return Boolean
227 * @return Boolean 214 */
228 */ 215 function isNull(value) {
229 function isNumber(value) { 216 return isType(value, "null");
230 return isType(value, "number"); 217 }
231 } 218 exports.isNull = isNull;
232 exports.isNumber = isNumber;
233 219
234 /** 220 /**
235 * Checks if value is a javascript Object 221 * Checks if value is a javascript Number
236 * 222 *
237 * @param mixed value 223 * @param mixed value
238 * @return Boolean 224 * @return Boolean
239 */ 225 */
240 function isObject(value) { 226 function isNumber(value) {
241 return isType(value, "object"); 227 return isType(value, "number");
242 } 228 }
243 exports.isObject = isObject; 229 exports.isNumber = isNumber;
244 230
245 /** 231 /**
246 * Checks if value is a javascript String 232 * Checks if value is a javascript Object
247 * 233 *
248 * @param mixed value 234 * @param mixed value
249 * @return Boolean 235 * @return Boolean
250 */ 236 */
251 function isString(value) { 237 function isObject(value) {
252 return isType(value, "string"); 238 var objectTypes = ["array", "object", "qtruntimeobject"];
253 } 239 return objectTypes.indexOf(betterTypeOf(value)) >= 0;
254 exports.isString = isString; 240 }
241 exports.isObject = isObject;
255 242
256 /** 243 /**
257 * Shorthands for checking if a value is of the given type. Can check for 244 * Checks if value is a javascript String
258 * arrays. 245 *
259 * 246 * @param mixed value
260 * @param mixed what The value to check 247 * @return Boolean
261 * @param String typeName The type name ("string", "number", "function", etc.) 248 */
262 * @return Boolean 249 function isString(value) {
263 */ 250 return isType(value, "string");
264 function isType(what, typeName) {
265 if (typeof typeName !== "string" || !typeName) {
266 throw new CasperError("You must pass isType() a typeName string");
267 } 251 }
268 return betterTypeOf(what).toLowerCase() === typeName.toLowerCase(); 252 exports.isString = isString;
269 }
270 exports.isType = isType;
271 253
272 /** 254 /**
273 * Checks if the provided value is undefined 255 * Shorthands for checking if a value is of the given type. Can check for
274 * 256 * arrays.
275 * @return Boolean 257 *
276 */ 258 * @param mixed what The value to check
277 function isUndefined(value) { 259 * @param String typeName The type name ("string", "number", "function", etc.)
278 return isType(value, "undefined"); 260 * @return Boolean
279 } 261 */
280 exports.isUndefined = isUndefined; 262 function isType(what, typeName) {
263 if (typeof typeName !== "string" || !typeName) {
264 throw new CasperError("You must pass isType() a typeName string");
265 }
266 return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
267 }
268 exports.isType = isType;
281 269
282 /** 270 /**
283 * Checks if the provided var is a WebPage instance 271 * Checks if the provided value is undefined
284 * 272 *
285 * @param mixed what 273 * @return Boolean
286 * @return Boolean 274 */
287 */ 275 function isUndefined(value) {
288 function isWebPage(what) { 276 return isType(value, "undefined");
289 if (!what || !isObject(what)) {
290 return false;
291 } 277 }
292 if (phantom.version.major <= 1 && phantom.version.minor < 3 && isFunction(require)) { 278 exports.isUndefined = isUndefined;
293 return what instanceof WebPage; 279
294 } else { 280 /**
295 return what.toString().indexOf('WebPage(') === 0; 281 * Checks if the provided var is a WebPage instance
282 *
283 * @param mixed what
284 * @return Boolean
285 */
286 function isWebPage(what) {
287 return betterTypeOf(what) === "qtruntimeobject" && what.objectName === 'WebPage';
296 } 288 }
297 } 289 exports.isWebPage = isWebPage;
298 exports.isWebPage = isWebPage;
299 290
300 /** 291 /**
301 * Object recursive merging utility. 292 * Object recursive merging utility.
302 * 293 *
303 * @param Object origin the origin object 294 * @param Object origin the origin object
304 * @param Object add the object to merge data into origin 295 * @param Object add the object to merge data into origin
305 * @return Object 296 * @return Object
306 */ 297 */
307 function mergeObjects(origin, add) { 298 function mergeObjects(origin, add) {
308 for (var p in add) { 299 for (var p in add) {
309 try { 300 try {
310 if (add[p].constructor === Object) { 301 if (add[p].constructor === Object) {
311 origin[p] = mergeObjects(origin[p], add[p]); 302 origin[p] = mergeObjects(origin[p], add[p]);
312 } else { 303 } else {
313 origin[p] = add[p]; 304 origin[p] = add[p];
305 }
306 } catch(e) {
307 origin[p] = add[p];
314 } 308 }
315 } catch(e) {
316 origin[p] = add[p];
317 } 309 }
310 return origin;
318 } 311 }
319 return origin; 312 exports.mergeObjects = mergeObjects;
320 }
321 exports.mergeObjects = mergeObjects;
322 313
323 /** 314 /**
324 * Creates an (SG|X)ML node element. 315 * Creates an (SG|X)ML node element.
325 * 316 *
326 * @param String name The node name 317 * @param String name The node name
327 * @param Object attributes Optional attributes 318 * @param Object attributes Optional attributes
328 * @return HTMLElement 319 * @return HTMLElement
329 */ 320 */
330 function node(name, attributes) { 321 function node(name, attributes) {
331 var _node = document.createElement(name); 322 var _node = document.createElement(name);
332 for (var attrName in attributes) { 323 for (var attrName in attributes) {
333 var value = attributes[attrName]; 324 var value = attributes[attrName];
334 if (attributes.hasOwnProperty(attrName) && isString(attrName)) { 325 if (attributes.hasOwnProperty(attrName) && isString(attrName)) {
335 _node.setAttribute(attrName, value); 326 _node.setAttribute(attrName, value);
327 }
336 } 328 }
329 return _node;
337 } 330 }
338 return _node; 331 exports.node = node;
339 }
340 exports.node = node;
341 332
342 /** 333 /**
343 * Serializes a value using JSON. 334 * Serializes a value using JSON.
344 * 335 *
345 * @param Mixed value 336 * @param Mixed value
346 * @return String 337 * @return String
347 */ 338 */
348 function serialize(value) { 339 function serialize(value) {
349 if (isArray(value)) { 340 if (isArray(value)) {
350 value = value.map(function _map(prop) { 341 value = value.map(function _map(prop) {
351 return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop; 342 return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop;
352 }); 343 });
344 }
345 return JSON.stringify(value, null, 4);
346 }
347 exports.serialize = serialize;
348
349 /**
350 * Returns unique values from an array.
351 *
352 * Note: ugly code is ugly, but efficient: http://jsperf.com/array-unique2/8
353 *
354 * @param Array array
355 * @return Array
356 */
357 function unique(array) {
358 var o = {},
359 r = [];
360 for (var i = 0, len = array.length; i !== len; i++) {
361 var d = array[i];
362 if (o[d] !== 1) {
363 o[d] = 1;
364 r[r.length] = d;
365 }
366 }
367 return r;
353 } 368 }
354 return JSON.stringify(value, null, 4); 369 exports.unique = unique;
355 } 370 })(exports);
356 exports.serialize = serialize;
......
This diff could not be displayed because it is too large.
...@@ -27,13 +27,15 @@ ...@@ -27,13 +27,15 @@
27 * DEALINGS IN THE SOFTWARE. 27 * DEALINGS IN THE SOFTWARE.
28 * 28 *
29 */ 29 */
30 /*global exports, phantom, require, CasperError*/ 30
31 /*global CasperError console exports phantom require*/
32
31 var utils = require('utils'); 33 var utils = require('utils');
32 var fs = require('fs'); 34 var fs = require('fs');
33 35
34 exports.create = function create() { 36 exports.create = function create() {
35 "use strict"; 37 "use strict";
36 return new this.XUnitExporter(); 38 return new XUnitExporter();
37 }; 39 };
38 40
39 /** 41 /**
......
1 casper = require("casper").create 1 casper = require("casper").create
2 verbose: true 2 verbose: true
3 3
4 # If we don't set a limit, it could go on forever
5 upTo = ~~casper.cli.get(0) || 10
6
7 ###
8 Fetch all <a> elements from the page and return
9 the ones which contains a href starting with 'http://'
10 ###
11 searchLinks = ->
12 filter = Array::filter
13 map = Array::map
14 map.call filter.call(document.querySelectorAll("a"), (a) ->
15 (/^http:\/\/.*/i).test a.getAttribute("href")
16 ), (a) ->
17 a.getAttribute "href"
18
19 # The base links array 4 # The base links array
20 links = [ 5 links = [
21 "http://google.com/" 6 "http://google.com/"
...@@ -23,10 +8,10 @@ links = [ ...@@ -23,10 +8,10 @@ links = [
23 "http://bing.com/" 8 "http://bing.com/"
24 ] 9 ]
25 10
26 # Just opens the page and prints the title 11 currentLink = 0;
27 start = (link) -> 12
28 @start link, -> 13 # If we don't set a limit, it could go on forever
29 @echo "Page title: #{ @getTitle() }" 14 upTo = ~~casper.cli.get(0) || 10
30 15
31 ### 16 ###
32 Get the links, and add them to the links array 17 Get the links, and add them to the links array
...@@ -38,12 +23,22 @@ addLinks = (link) -> ...@@ -38,12 +23,22 @@ addLinks = (link) ->
38 @echo "#{found.length} links found on #{link}" 23 @echo "#{found.length} links found on #{link}"
39 links = links.concat found 24 links = links.concat found
40 25
41 casper.start() 26 ###
42 27 Fetch all <a> elements from the page and return
43 casper.then -> 28 the ones which contains a href starting with 'http://'
44 @echo "Starting" 29 ###
30 searchLinks = ->
31 filter = Array::filter
32 map = Array::map
33 map.call filter.call(document.querySelectorAll("a"), (a) ->
34 (/^http:\/\/.*/i).test a.getAttribute("href")
35 ), (a) ->
36 a.getAttribute "href"
45 37
46 currentLink = 0; 38 # Just opens the page and prints the title
39 start = (link) ->
40 @start link, ->
41 @echo "Page title: #{ @getTitle() }"
47 42
48 # As long as it has a next link, and is under the maximum limit, will keep running 43 # As long as it has a next link, and is under the maximum limit, will keep running
49 check = -> 44 check = ->
...@@ -57,4 +52,9 @@ check = -> ...@@ -57,4 +52,9 @@ check = ->
57 @echo "All done." 52 @echo "All done."
58 @exit() 53 @exit()
59 54
55 casper.start()
56
57 casper.then ->
58 @echo "Starting"
59
60 casper.run check 60 casper.run check
......
...@@ -2,24 +2,6 @@ var casper = require("casper").create({ ...@@ -2,24 +2,6 @@ var casper = require("casper").create({
2 verbose: true 2 verbose: true
3 }); 3 });
4 4
5 // If we don't set a limit, it could go on forever
6 var upTo = ~~casper.cli.get(0) || 10;
7
8 /*
9 Fetch all <a> elements from the page and return
10 the ones which contains a href starting with 'http://'
11 */
12 var searchLinks = function() {
13 var filter, map;
14 filter = Array.prototype.filter;
15 map = Array.prototype.map;
16 return map.call(filter.call(document.querySelectorAll("a"), function(a) {
17 return /^http:\/\/.*/i.test(a.getAttribute("href"));
18 }), function(a) {
19 return a.getAttribute("href");
20 });
21 };
22
23 // The base links array 5 // The base links array
24 var links = [ 6 var links = [
25 "http://google.com/", 7 "http://google.com/",
...@@ -27,30 +9,40 @@ var links = [ ...@@ -27,30 +9,40 @@ var links = [
27 "http://bing.com/" 9 "http://bing.com/"
28 ]; 10 ];
29 11
30 // Just opens the page and prints the title 12 // If we don't set a limit, it could go on forever
31 var start = function(link) { 13 var upTo = ~~casper.cli.get(0) || 10;
32 this.start(link, function() { 14
33 this.echo('Page title: ' + this.getTitle()); 15 var currentLink = 0;
34 });
35 };
36 16
37 // Get the links, and add them to the links array 17 // Get the links, and add them to the links array
38 // (It could be done all in one step, but it is intentionally splitted) 18 // (It could be done all in one step, but it is intentionally splitted)
39 var addLinks = function(link) { 19 function addLinks(link) {
40 this.then(function() { 20 this.then(function() {
41 var found = this.evaluate(searchLinks); 21 var found = this.evaluate(searchLinks);
42 this.echo(found.length + " links found on " + link); 22 this.echo(found.length + " links found on " + link);
43 links = links.concat(found); 23 links = links.concat(found);
44 }); 24 });
45 }; 25 }
46 26
47 casper.start(); 27 // Fetch all <a> elements from the page and return
48 28 // the ones which contains a href starting with 'http://'
49 casper.then(function() { 29 function searchLinks() {
50 this.echo("Starting"); 30 var filter, map;
51 }); 31 filter = Array.prototype.filter;
32 map = Array.prototype.map;
33 return map.call(filter.call(document.querySelectorAll("a"), function(a) {
34 return (/^http:\/\/.*/i).test(a.getAttribute("href"));
35 }), function(a) {
36 return a.getAttribute("href");
37 });
38 }
52 39
53 var currentLink = 0; 40 // Just opens the page and prints the title
41 function start(link) {
42 this.start(link, function() {
43 this.echo('Page title: ' + this.getTitle());
44 });
45 }
54 46
55 // As long as it has a next link, and is under the maximum limit, will keep running 47 // As long as it has a next link, and is under the maximum limit, will keep running
56 function check() { 48 function check() {
...@@ -64,6 +56,10 @@ function check() { ...@@ -64,6 +56,10 @@ function check() {
64 this.echo("All done."); 56 this.echo("All done.");
65 this.exit(); 57 this.exit();
66 } 58 }
67 }; 59 }
60
61 casper.start().then(function() {
62 this.echo("Starting");
63 });
68 64
69 casper.run(check); 65 casper.run(check);
......
...@@ -6,24 +6,47 @@ if (!phantom.casperLoaded) { ...@@ -6,24 +6,47 @@ if (!phantom.casperLoaded) {
6 var fs = require('fs'); 6 var fs = require('fs');
7 var utils = require('utils'); 7 var utils = require('utils');
8 var f = utils.format; 8 var f = utils.format;
9 var includes = [];
10 var tests = [];
9 var casper = require('casper').create({ 11 var casper = require('casper').create({
10 exitOnError: false 12 exitOnError: false
11 }); 13 });
12 14
13 // Options from cli 15 // local utils
16 function checkIncludeFile(include) {
17 var absInclude = fs.absolute(include.trim());
18 if (!fs.exists(absInclude)) {
19 casper.warn("%s file not found, can't be included", absInclude);
20 return;
21 }
22 if (!utils.isJsFile(absInclude)) {
23 casper.warn("%s is not a supported file type, can't be included", absInclude);
24 return;
25 }
26 if (fs.isDirectory(absInclude)) {
27 casper.warn("%s is a directory, can't be included", absInclude);
28 return;
29 }
30 if (tests.indexOf(include) > -1 || tests.indexOf(absInclude) > -1) {
31 casper.warn("%s is a test file, can't be included", absInclude);
32 return;
33 }
34 return absInclude;
35 }
36
37 // parse some options from cli
14 casper.options.verbose = casper.cli.get('direct') || false; 38 casper.options.verbose = casper.cli.get('direct') || false;
15 casper.options.logLevel = casper.cli.get('log-level') || "error"; 39 casper.options.logLevel = casper.cli.get('log-level') || "error";
16 40
17 // Overriding Casper.open to prefix all test urls 41 // overriding Casper.open to prefix all test urls
18 casper.setFilter('open.location', function(location) { 42 casper.setFilter('open.location', function(location) {
19 if (!/^http/.test(location)) { 43 if (!/^http/.test(location)) {
20 return f('file://%s/%s', phantom.casperPath, location); 44 return f('file://%s/%s', fs.workingDirectory, location);
21 } 45 }
22 return location; 46 return location;
23 }); 47 });
24 48
25 var tests = []; 49 // test paths are passed as args
26
27 if (casper.cli.args.length) { 50 if (casper.cli.args.length) {
28 tests = casper.cli.args.filter(function(path) { 51 tests = casper.cli.args.filter(function(path) {
29 return fs.isFile(path) || fs.isDirectory(path); 52 return fs.isFile(path) || fs.isDirectory(path);
...@@ -33,8 +56,21 @@ if (casper.cli.args.length) { ...@@ -33,8 +56,21 @@ if (casper.cli.args.length) {
33 casper.exit(1); 56 casper.exit(1);
34 } 57 }
35 58
59 // includes handling
60 if (casper.cli.has('includes')) {
61 includes = casper.cli.get('includes').split(',').map(function(include) {
62 // we can't use filter() directly because of abspath transformation
63 return checkIncludeFile(include);
64 }).filter(function(include) {
65 return utils.isString(include);
66 });
67 casper.test.includes = utils.unique(includes);
68 }
69
70 // test suites completion listener
36 casper.test.on('tests.complete', function() { 71 casper.test.on('tests.complete', function() {
37 this.renderResults(true, undefined, casper.cli.get('xunit') || undefined); 72 this.renderResults(true, undefined, casper.cli.get('xunit') || undefined);
38 }); 73 });
39 74
75 // run all the suites
40 casper.test.runSuites.apply(casper.test, tests); 76 casper.test.runSuites.apply(casper.test, tests);
......
1 <!DOCTYPE html> 1 <!DOCTYPE html>
2 <html> 2 <html>
3 <head> 3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <title>CasperJS test form</title> 5 <title>CasperJS test form</title>
6 </head> 6 </head>
7 <body> 7 <body>
8 <form action="result.html" enctype="multipart/form-data"> 8 <form action="result.html" enctype="multipart/form-data">
9 <input type="text" name="email" placeholder="email" /> 9 <input type="text" name="email" placeholder="email">
10 <input type="password" name="password" placeholder="password">
10 <textarea name="content"></textarea> 11 <textarea name="content"></textarea>
11 <select name="topic"> 12 <select name="topic">
12 <option>foo</option> 13 <option>foo</option>
13 <option value="bar">baz</option> 14 <option value="bar">baz</option>
14 </select> 15 </select>
15 <input type="checkbox" name="check" /> 16 <input type="checkbox" name="check">
16 <input type="radio" name="choice" value="yes"/> 17 <input type="radio" name="choice" value="yes">
17 <input type="radio" name="choice" value="no"/> 18 <input type="radio" name="choice" value="no">
18 <input type="file" name="file"/> 19 <input type="file" name="file">
19 <input type="checkbox" name="checklist[]" value="1" /> 20 <input type="checkbox" name="checklist[]" value="1">
20 <input type="checkbox" name="checklist[]" value="2" /> 21 <input type="checkbox" name="checklist[]" value="2">
21 <input type="checkbox" name="checklist[]" value="3" /> 22 <input type="checkbox" name="checklist[]" value="3">
22 <input type="submit" name="submit" value="submit" /> 23 <input type="submit" name="submit" value="submit">
23 </form> 24 </form>
24 </body> 25 </body>
25 </html> 26 </html>
......
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Multiple forms test</title>
5 </head>
6 <body>
7 <form name="f1">
8 <input type="hidden" name="f" value="f1">
9 <input type="text" name="yo">
10 </form>
11 <form name="f2">
12 <input type="hidden" name="f" value="f2">
13 <input type="text" name="yo">
14 </form>
15 </body>
16 </html>
1 function testUA(ua, match) {
2 casper.test.assertMatch(
3 ua, match, 'Default user agent matches ' + match
4 );
5 }
6
7 function fetchUA(request) {
8 testUA(request.headers.filter(function(header) {
9 return header.name === "User-Agent";
10 }).pop().value, /plop/);
11 }
12
13 testUA(casper.options.pageSettings.userAgent, /CasperJS/);
14
15 casper.start();
16
17 casper.userAgent('plop').on('resource.requested', fetchUA);
18
19 casper.thenOpen('tests/site/index.html');
20
21 casper.run(function() {
22 this.removeListener('resource.requested', fetchUA);
23 this.test.done();
24 });
1 var fs = require('fs');
2 var clientutils = require('clientutils').create();
3
4 var testCases = {
5 'an empty string': '',
6 'a word': 'plop',
7 'a null char': 'a\u0000',
8 'an utf8 string': 'ÀÁÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðòóôõöùúûüýÿ',
9 'song lyrics': ("Voilà l'été, j'aperçois le soleil\n" +
10 "Les nuages filent et le ciel s'éclaircit\n" +
11 "Et dans ma tête qui bourdonnent?\n" +
12 "Les abeilles!"),
13 'a file contents': fs.read(phantom.casperPath + '/tests/site/alert.html')
14 };
15
16 casper.test.comment('ClientUtils.encode()');
17
18 for (var what in testCases) {
19 var source = testCases[what];
20 var encoded = clientutils.encode(source);
21 casper.test.assertEquals(clientutils.decode(encoded), source, 'ClientUtils can encode and decode ' + what);
22 }
23
24 casper.test.done();
...@@ -2,6 +2,7 @@ casper.start('tests/site/form.html', function() { ...@@ -2,6 +2,7 @@ casper.start('tests/site/form.html', function() {
2 this.test.comment('Casper.fill()'); 2 this.test.comment('Casper.fill()');
3 this.fill('form[action="result.html"]', { 3 this.fill('form[action="result.html"]', {
4 email: 'chuck@norris.com', 4 email: 'chuck@norris.com',
5 password: 'chuck',
5 content: 'Am watching thou', 6 content: 'Am watching thou',
6 check: true, 7 check: true,
7 choice: 'no', 8 choice: 'no',
...@@ -13,6 +14,9 @@ casper.start('tests/site/form.html', function() { ...@@ -13,6 +14,9 @@ casper.start('tests/site/form.html', function() {
13 return document.querySelector('input[name="email"]').value; 14 return document.querySelector('input[name="email"]').value;
14 }, 'chuck@norris.com', 'Casper.fill() can fill an input[type=text] form field'); 15 }, 'chuck@norris.com', 'Casper.fill() can fill an input[type=text] form field');
15 this.test.assertEvalEquals(function() { 16 this.test.assertEvalEquals(function() {
17 return document.querySelector('input[name="password"]').value;
18 }, 'chuck', 'Casper.fill() can fill an input[type=password] form field');
19 this.test.assertEvalEquals(function() {
16 return document.querySelector('textarea[name="content"]').value; 20 return document.querySelector('textarea[name="content"]').value;
17 }, 'Am watching thou', 'Casper.fill() can fill a textarea form field'); 21 }, 'Am watching thou', 'Casper.fill() can fill a textarea form field');
18 this.test.assertEvalEquals(function() { 22 this.test.assertEvalEquals(function() {
...@@ -41,12 +45,25 @@ casper.start('tests/site/form.html', function() { ...@@ -41,12 +45,25 @@ casper.start('tests/site/form.html', function() {
41 casper.then(function() { 45 casper.then(function() {
42 this.test.comment('Form submitted'); 46 this.test.comment('Form submitted');
43 this.test.assertUrlMatch(/email=chuck@norris.com/, 'Casper.fill() input[type=email] field was submitted'); 47 this.test.assertUrlMatch(/email=chuck@norris.com/, 'Casper.fill() input[type=email] field was submitted');
48 this.test.assertUrlMatch(/password=chuck/, 'Casper.fill() input[type=password] field was submitted');
44 this.test.assertUrlMatch(/content=Am\+watching\+thou/, 'Casper.fill() textarea field was submitted'); 49 this.test.assertUrlMatch(/content=Am\+watching\+thou/, 'Casper.fill() textarea field was submitted');
45 this.test.assertUrlMatch(/check=on/, 'Casper.fill() input[type=checkbox] field was submitted'); 50 this.test.assertUrlMatch(/check=on/, 'Casper.fill() input[type=checkbox] field was submitted');
46 this.test.assertUrlMatch(/choice=no/, 'Casper.fill() input[type=radio] field was submitted'); 51 this.test.assertUrlMatch(/choice=no/, 'Casper.fill() input[type=radio] field was submitted');
47 this.test.assertUrlMatch(/topic=bar/, 'Casper.fill() select field was submitted'); 52 this.test.assertUrlMatch(/topic=bar/, 'Casper.fill() select field was submitted');
48 }); 53 });
49 54
55 // multiple forms
56 casper.thenOpen('tests/site/multiple-forms.html', function() {
57 this.test.comment('Multiple forms');
58 this.fill('form[name="f2"]', {
59 yo: "ok"
60 }, true);
61 });
62
63 casper.then(function() {
64 this.test.assertUrlMatch(/\?f=f2&yo=ok$/, 'Casper.fill() handles multiple forms');
65 }),
66
50 casper.run(function() { 67 casper.run(function() {
51 this.test.done(); 68 this.test.done();
52 }); 69 });
......
1 var fs = require('fs');
2 var x = require('casper').selectXPath;
3
4 function fakeDocument(html) {
5 window.document.body.innerHTML = html;
6 }
7
8 (function(casper) {
9 casper.test.comment('ClientUtils.encode()');
10 var clientutils = require('clientutils').create();
11 var testCases = {
12 'an empty string': '',
13 'a word': 'plop',
14 'a null char': 'a\u0000',
15 'an utf8 string': 'ÀÁÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðòóôõöùúûüýÿ',
16 'song lyrics': ("Voilà l'été, j'aperçois le soleil\n" +
17 "Les nuages filent et le ciel s'éclaircit\n" +
18 "Et dans ma tête qui bourdonnent?\n" +
19 "Les abeilles!"),
20 'a file contents': fs.read(phantom.casperPath + '/tests/site/alert.html')
21 };
22 for (var what in testCases) {
23 var source = testCases[what];
24 var encoded = clientutils.encode(source);
25 casper.test.assertEquals(clientutils.decode(encoded), source, 'ClientUtils.encode() encodes and decodes ' + what);
26 }
27 })(casper);
28
29 (function(casper) {
30 casper.test.comment('ClientUtils.exists()');
31 var clientutils = require('clientutils').create();
32 fakeDocument('<ul class="foo"><li>bar</li><li>baz</li></ul>');
33 casper.test.assert(clientutils.exists('ul'), 'ClientUtils.exists() checks that an element exist');
34 casper.test.assertNot(clientutils.exists('ol'), 'ClientUtils.exists() checks that an element exist');
35 casper.test.assert(clientutils.exists('ul.foo li'), 'ClientUtils.exists() checks that an element exist');
36 // xpath
37 casper.test.assert(clientutils.exists(x('//ul')), 'ClientUtils.exists() checks that an element exist using XPath');
38 casper.test.assertNot(clientutils.exists(x('//ol')), 'ClientUtils.exists() checks that an element exist using XPath');
39 fakeDocument(null);
40 })(casper);
41
42 (function(casper) {
43 casper.test.comment('ClientUtils.findAll()');
44 var clientutils = require('clientutils').create();
45 fakeDocument('<ul class="foo"><li>bar</li><li>baz</li></ul>');
46 casper.test.assertType(clientutils.findAll('li'), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements');
47 casper.test.assertEquals(clientutils.findAll('li').length, 2, 'ClientUtils.findAll() can find matching DOM elements');
48 casper.test.assertType(clientutils.findAll('ol'), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements');
49 casper.test.assertEquals(clientutils.findAll('ol').length, 0, 'ClientUtils.findAll() can find matching DOM elements');
50 // scoped
51 var scope = clientutils.findOne('ul');
52 casper.test.assertType(clientutils.findAll('li', scope), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements within a given scope');
53 fakeDocument(null);
54 })(casper);
55
56 (function(casper) {
57 casper.test.comment('ClientUtils.findOne()');
58 var clientutils = require('clientutils').create();
59 fakeDocument('<ul class="foo"><li>bar</li><li>baz</li></ul>');
60 casper.test.assertType(clientutils.findOne('ul'), 'htmlulistelement', 'ClientUtils.findOne() can find a matching DOM element');
61 casper.test.assertNot(clientutils.findOne('ol'), 'ClientUtils.findOne() can find a matching DOM element');
62 // scoped
63 var scope = clientutils.findOne('ul');
64 casper.test.assertType(clientutils.findAll('li', scope), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements within a given scope');
65 casper.test.assertEquals(clientutils.findAll('li', scope).length, 2, 'ClientUtils.findAll() can find matching DOM elements within a given scope');
66 fakeDocument(null);
67 })(casper);
68
69 (function(casper) {
70 casper.test.comment('ClientUtils.processSelector()');
71 var clientutils = require('clientutils').create();
72 // CSS3 selector
73 var cssSelector = clientutils.processSelector('html body > ul.foo li');
74 casper.test.assertType(cssSelector, 'object', 'ClientUtils.processSelector() can process a CSS3 selector');
75 casper.test.assertEquals(cssSelector.type, 'css', 'ClientUtils.processSelector() can process a CSS3 selector');
76 casper.test.assertEquals(cssSelector.path, 'html body > ul.foo li', 'ClientUtils.processSelector() can process a CSS3 selector');
77 // XPath selector
78 var xpathSelector = clientutils.processSelector(x('//li[text()="blah"]'));
79 casper.test.assertType(xpathSelector, 'object', 'ClientUtils.processSelector() can process a XPath selector');
80 casper.test.assertEquals(xpathSelector.type, 'xpath', 'ClientUtils.processSelector() can process a XPath selector');
81 casper.test.assertEquals(xpathSelector.path, '//li[text()="blah"]', 'ClientUtils.processSelector() can process a XPath selector');
82 })(casper);
83
84 casper.test.done();
...@@ -96,6 +96,9 @@ casper.then(function() { ...@@ -96,6 +96,9 @@ casper.then(function() {
96 t.comment('Tester.assertTitle()'); 96 t.comment('Tester.assertTitle()');
97 t.assertTitle('CasperJS test index', 'Tester.assertTitle() works as expected'); 97 t.assertTitle('CasperJS test index', 'Tester.assertTitle() works as expected');
98 98
99 t.comment('Tester.assertTitleMatch()');
100 t.assertTitleMatch(/test index/, 'Tester.assertTitleMatch() works as expected');
101
99 t.comment('Tester.assertType()'); 102 t.comment('Tester.assertType()');
100 t.assertType("plop", "string", "Tester.assertType() works as expected"); 103 t.assertType("plop", "string", "Tester.assertType() works as expected");
101 104
......
...@@ -29,6 +29,13 @@ t.comment('fillBlanks()'); ...@@ -29,6 +29,13 @@ t.comment('fillBlanks()');
29 } 29 }
30 })(); 30 })();
31 31
32 t.comment('isArray()');
33 (function() {
34 t.assertEquals(utils.isArray([]), true, 'isArray() checks for an Array');
35 t.assertEquals(utils.isArray({}), false, 'isArray() checks for an Array');
36 t.assertEquals(utils.isArray("foo"), false, 'isArray() checks for an Array');
37 })();
38
32 t.comment('isClipRect()'); 39 t.comment('isClipRect()');
33 (function() { 40 (function() {
34 testCases = [ 41 testCases = [
...@@ -44,6 +51,26 @@ t.comment('isClipRect()'); ...@@ -44,6 +51,26 @@ t.comment('isClipRect()');
44 }); 51 });
45 })(); 52 })();
46 53
54 t.comment('isObject()');
55 (function() {
56 t.assertEquals(utils.isObject({}), true, 'isObject() checks for an Object');
57 t.assertEquals(utils.isObject([]), true, 'isObject() checks for an Object');
58 t.assertEquals(utils.isObject(1), false, 'isObject() checks for an Object');
59 t.assertEquals(utils.isObject("1"), false, 'isObject() checks for an Object');
60 t.assertEquals(utils.isObject(function(){}), false, 'isObject() checks for an Object');
61 t.assertEquals(utils.isObject(new Function('return {};')()), true, 'isObject() checks for an Object');
62 t.assertEquals(utils.isObject(require('webpage').create()), true, 'isObject() checks for an Object');
63 t.assertEquals(utils.isObject(null), false, 'isObject() checks for an Object');
64 })();
65
66 t.comment('isWebPage()');
67 (function() {
68 var pageModule = require('webpage');
69 t.assertEquals(utils.isWebPage(pageModule), false, 'isWebPage() checks for a WebPage instance');
70 t.assertEquals(utils.isWebPage(pageModule.create()), true, 'isWebPage() checks for a WebPage instance');
71 t.assertEquals(utils.isWebPage(null), false, 'isWebPage() checks for a WebPage instance');
72 })();
73
47 t.comment('isJsFile()'); 74 t.comment('isJsFile()');
48 (function() { 75 (function() {
49 testCases = { 76 testCases = {
...@@ -90,4 +117,29 @@ t.comment('mergeObjects()'); ...@@ -90,4 +117,29 @@ t.comment('mergeObjects()');
90 }); 117 });
91 })(); 118 })();
92 119
120 t.comment('unique()');
121 (function() {
122 testCases = [
123 {
124 input: [1,2,3],
125 output: [1,2,3]
126 },
127 {
128 input: [1,2,3,2,1],
129 output: [1,2,3]
130 },
131 {
132 input: ["foo", "bar", "foo"],
133 output: ["foo", "bar"]
134 },
135 {
136 input: [],
137 output: []
138 }
139 ];
140 testCases.forEach(function(testCase) {
141 t.assertEquals(utils.unique(testCase.input), testCase.output, 'unique() computes unique values of an array');
142 });
143 })();
144
93 t.done(); 145 t.done();
......