casperjs 1.0.0.RC1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. data/CHANGELOG.md +179 -0
  2. data/LICENSE.md +19 -0
  3. data/README.md +30 -0
  4. data/bin/bootstrap.js +292 -0
  5. data/bin/usage.txt +10 -0
  6. data/casperjs.gemspec +21 -0
  7. data/modules/casper.js +1679 -0
  8. data/modules/cli.js +138 -0
  9. data/modules/clientutils.js +595 -0
  10. data/modules/colorizer.js +129 -0
  11. data/modules/events.js +247 -0
  12. data/modules/injector.js +93 -0
  13. data/modules/mouse.js +110 -0
  14. data/modules/querystring.js +187 -0
  15. data/modules/tester.js +807 -0
  16. data/modules/utils.js +429 -0
  17. data/modules/vendors/coffee-script.js +8 -0
  18. data/modules/xunit.js +123 -0
  19. data/package.json +35 -0
  20. data/rubybin/casperjs +57 -0
  21. data/samples/bbcshots.coffee +64 -0
  22. data/samples/bbcshots.js +80 -0
  23. data/samples/cliplay.coffee +19 -0
  24. data/samples/cliplay.js +21 -0
  25. data/samples/customevents.coffee +11 -0
  26. data/samples/customevents.js +13 -0
  27. data/samples/customlogging.coffee +33 -0
  28. data/samples/customlogging.js +42 -0
  29. data/samples/download.coffee +10 -0
  30. data/samples/download.js +11 -0
  31. data/samples/dynamic.coffee +60 -0
  32. data/samples/dynamic.js +65 -0
  33. data/samples/each.coffee +14 -0
  34. data/samples/each.js +17 -0
  35. data/samples/events.coffee +34 -0
  36. data/samples/events.js +41 -0
  37. data/samples/extends.coffee +29 -0
  38. data/samples/extends.js +37 -0
  39. data/samples/googlelinks.coffee +27 -0
  40. data/samples/googlelinks.js +33 -0
  41. data/samples/googlematch.coffee +47 -0
  42. data/samples/googlematch.js +65 -0
  43. data/samples/googlepagination.coffee +40 -0
  44. data/samples/googlepagination.js +51 -0
  45. data/samples/googletesting.coffee +17 -0
  46. data/samples/googletesting.js +23 -0
  47. data/samples/logcolor.coffee +10 -0
  48. data/samples/logcolor.js +11 -0
  49. data/samples/metaextract.coffee +23 -0
  50. data/samples/metaextract.js +29 -0
  51. data/samples/multirun.coffee +37 -0
  52. data/samples/multirun.js +56 -0
  53. data/samples/screenshot.coffee +28 -0
  54. data/samples/screenshot.js +33 -0
  55. data/samples/statushandlers.coffee +15 -0
  56. data/samples/statushandlers.js +19 -0
  57. data/samples/steptimeout.coffee +37 -0
  58. data/samples/steptimeout.js +45 -0
  59. data/samples/timeout.coffee +39 -0
  60. data/samples/timeout.js +47 -0
  61. data/tests/run.js +76 -0
  62. data/tests/site/alert.html +10 -0
  63. data/tests/site/click.html +40 -0
  64. data/tests/site/confirm.html +12 -0
  65. data/tests/site/elementattribute.html +6 -0
  66. data/tests/site/error.html +10 -0
  67. data/tests/site/form.html +26 -0
  68. data/tests/site/global.html +9 -0
  69. data/tests/site/images/phantom.png +0 -0
  70. data/tests/site/index.html +17 -0
  71. data/tests/site/mouse-events.html +47 -0
  72. data/tests/site/multiple-forms.html +16 -0
  73. data/tests/site/page1.html +8 -0
  74. data/tests/site/page2.html +8 -0
  75. data/tests/site/page3.html +8 -0
  76. data/tests/site/prompt.html +12 -0
  77. data/tests/site/resources.html +16 -0
  78. data/tests/site/result.html +11 -0
  79. data/tests/site/test.html +10 -0
  80. data/tests/site/visible.html +17 -0
  81. data/tests/site/waitFor.html +22 -0
  82. data/tests/suites/casper/agent.js +24 -0
  83. data/tests/suites/casper/capture.js +31 -0
  84. data/tests/suites/casper/click.js +61 -0
  85. data/tests/suites/casper/confirm.js +21 -0
  86. data/tests/suites/casper/elementattribute.js +8 -0
  87. data/tests/suites/casper/encode.js +20 -0
  88. data/tests/suites/casper/evaluate.js +27 -0
  89. data/tests/suites/casper/events.js +38 -0
  90. data/tests/suites/casper/exists.js +9 -0
  91. data/tests/suites/casper/fetchtext.js +9 -0
  92. data/tests/suites/casper/flow.coffee +38 -0
  93. data/tests/suites/casper/formfill.js +69 -0
  94. data/tests/suites/casper/global.js +9 -0
  95. data/tests/suites/casper/history.js +21 -0
  96. data/tests/suites/casper/hooks.js +41 -0
  97. data/tests/suites/casper/logging.js +38 -0
  98. data/tests/suites/casper/mouseevents.js +27 -0
  99. data/tests/suites/casper/onerror.js +19 -0
  100. data/tests/suites/casper/open.js +73 -0
  101. data/tests/suites/casper/prompt.js +17 -0
  102. data/tests/suites/casper/resources.coffee +24 -0
  103. data/tests/suites/casper/start.js +15 -0
  104. data/tests/suites/casper/steps.js +32 -0
  105. data/tests/suites/casper/viewport.js +11 -0
  106. data/tests/suites/casper/visible.js +17 -0
  107. data/tests/suites/casper/wait.js +27 -0
  108. data/tests/suites/casper/xpath.js +32 -0
  109. data/tests/suites/cli.js +125 -0
  110. data/tests/suites/clientutils.js +84 -0
  111. data/tests/suites/coffee.coffee +19 -0
  112. data/tests/suites/fs.js +36 -0
  113. data/tests/suites/http_status.js +28 -0
  114. data/tests/suites/injector.js +64 -0
  115. data/tests/suites/tester.js +121 -0
  116. data/tests/suites/utils.js +209 -0
  117. data/tests/suites/xunit.js +16 -0
  118. data/tests/testdir/01_a/abc.js +0 -0
  119. data/tests/testdir/01_a/def.js +0 -0
  120. data/tests/testdir/02_b/abc.js +0 -0
  121. data/tests/testdir/03_a.js +0 -0
  122. data/tests/testdir/03_b.js +0 -0
  123. data/tests/testdir/04/01_init.js +0 -0
  124. data/tests/testdir/04/02_do.js +0 -0
  125. metadata +192 -0
@@ -0,0 +1,138 @@
1
+ /*!
2
+ * Casper is a navigation utility for PhantomJS.
3
+ *
4
+ * Documentation: http://casperjs.org/
5
+ * Repository: http://github.com/n1k0/casperjs
6
+ *
7
+ * Copyright (c) 2011-2012 Nicolas Perriault
8
+ *
9
+ * Part of source code is Copyright Joyent, Inc. and other Node contributors.
10
+ *
11
+ * Permission is hereby granted, free of charge, to any person obtaining a
12
+ * copy of this software and associated documentation files (the "Software"),
13
+ * to deal in the Software without restriction, including without limitation
14
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
+ * and/or sell copies of the Software, and to permit persons to whom the
16
+ * Software is furnished to do so, subject to the following conditions:
17
+ *
18
+ * The above copyright notice and this permission notice shall be included
19
+ * in all copies or substantial portions of the Software.
20
+ *
21
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
+ * DEALINGS IN THE SOFTWARE.
28
+ *
29
+ */
30
+
31
+ /*global CasperError console exports phantom require*/
32
+
33
+ var system = require('system');
34
+ var utils = require('utils');
35
+
36
+ /**
37
+ * Extracts, normalize ad organize PhantomJS CLI arguments in a dedicated
38
+ * Object.
39
+ *
40
+ * @param array phantomArgs system.args value
41
+ * @return Object
42
+ */
43
+ exports.parse = function parse(phantomArgs) {
44
+ "use strict";
45
+ var extract = {
46
+ args: [],
47
+ options: {},
48
+ raw: {
49
+ args: [],
50
+ options: {}
51
+ },
52
+ drop: function drop(what) {
53
+ if (utils.isNumber(what)) {
54
+ // deleting an arg by its position
55
+ this.args = this.args.filter(function _filter(arg, index) {
56
+ return index !== what;
57
+ });
58
+ } else if (utils.isString(what)) {
59
+ // deleting an arg by its value
60
+ this.args = this.args.filter(function _filter(arg) {
61
+ return arg !== what;
62
+ });
63
+ // deleting an option by its name (key)
64
+ var self = this;
65
+ Object.keys(this.options).forEach(function _forEach(option) {
66
+ if (option === what) {
67
+ delete self.options[what];
68
+ }
69
+ });
70
+ } else {
71
+ throw new CasperError("cannot drop argument of type " + typeof what);
72
+ }
73
+ },
74
+ has: function has(what) {
75
+ if (utils.isNumber(what)) {
76
+ return what in this.args;
77
+ } else if (utils.isString(what)) {
78
+ return what in this.options;
79
+ } else {
80
+ throw new CasperError("Unsupported cli arg tester " + typeof what);
81
+ }
82
+ },
83
+ get: function get(what) {
84
+ if (utils.isNumber(what)) {
85
+ return this.args[what];
86
+ } else if (utils.isString(what)) {
87
+ return this.options[what];
88
+ } else {
89
+ throw new CasperError("Unsupported cli arg getter " + typeof what);
90
+ }
91
+ }
92
+ };
93
+ phantomArgs.forEach(function _forEach(arg) {
94
+ if (arg.indexOf('--') === 0) {
95
+ // named option
96
+ var optionMatch = arg.match(/^--(.*?)=(.*)/i);
97
+ if (optionMatch) {
98
+ extract.options[optionMatch[1]] = castArgument(optionMatch[2]);
99
+ extract.raw.options[optionMatch[1]] = optionMatch[2];
100
+ } else {
101
+ // flag
102
+ var flagMatch = arg.match(/^--(.*)/);
103
+ if (flagMatch) {
104
+ extract.options[flagMatch[1]] = extract.raw.options[flagMatch[1]] = true;
105
+ }
106
+ }
107
+ } else {
108
+ // positional arg
109
+ extract.args.push(castArgument(arg));
110
+ extract.raw.args.push(castArgument(arg));
111
+ }
112
+ });
113
+ extract.raw = utils.mergeObjects(extract.raw, {
114
+ drop: extract.drop,
115
+ has: extract.has,
116
+ get: extract.get
117
+ });
118
+ return extract;
119
+ };
120
+
121
+ /**
122
+ * Cast a string argument to its typed equivalent.
123
+ *
124
+ * @param String arg
125
+ * @return Mixed
126
+ */
127
+ function castArgument(arg) {
128
+ "use strict";
129
+ if (arg.match(/^-?\d+$/)) {
130
+ return parseInt(arg, 10);
131
+ } else if (arg.match(/^-?\d+\.\d+$/)) {
132
+ return parseFloat(arg);
133
+ } else if (arg.match(/^(true|false)$/i)) {
134
+ return arg.trim().toLowerCase() === "true" ? true : false;
135
+ } else {
136
+ return arg;
137
+ }
138
+ }
@@ -0,0 +1,595 @@
1
+ /*!
2
+ * Casper is a navigation utility for PhantomJS.
3
+ *
4
+ * Documentation: http://casperjs.org/
5
+ * Repository: http://github.com/n1k0/casperjs
6
+ *
7
+ * Copyright (c) 2011-2012 Nicolas Perriault
8
+ *
9
+ * Part of source code is Copyright Joyent, Inc. and other Node contributors.
10
+ *
11
+ * Permission is hereby granted, free of charge, to any person obtaining a
12
+ * copy of this software and associated documentation files (the "Software"),
13
+ * to deal in the Software without restriction, including without limitation
14
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
+ * and/or sell copies of the Software, and to permit persons to whom the
16
+ * Software is furnished to do so, subject to the following conditions:
17
+ *
18
+ * The above copyright notice and this permission notice shall be included
19
+ * in all copies or substantial portions of the Software.
20
+ *
21
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
+ * DEALINGS IN THE SOFTWARE.
28
+ *
29
+ */
30
+
31
+ /*global console escape exports NodeList window*/
32
+
33
+ (function(exports) {
34
+ "use strict";
35
+
36
+ exports.create = function create(options) {
37
+ return new this.ClientUtils(options);
38
+ };
39
+
40
+ /**
41
+ * Casper client-side helpers.
42
+ */
43
+ exports.ClientUtils = function ClientUtils(options) {
44
+ // private members
45
+ var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
46
+ var BASE64_DECODE_CHARS = new Array(
47
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
48
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
49
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
50
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
51
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
52
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
53
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
54
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
55
+ );
56
+ var SUPPORTED_SELECTOR_TYPES = ['css', 'xpath'];
57
+
58
+ // public members
59
+ this.options = options || {};
60
+
61
+ /**
62
+ * Clicks on the DOM element behind the provided selector.
63
+ *
64
+ * @param String selector A CSS3 selector to the element to click
65
+ * @return Boolean
66
+ */
67
+ this.click = function click(selector) {
68
+ return this.mouseEvent('click', selector);
69
+ };
70
+
71
+ /**
72
+ * Decodes a base64 encoded string. Succeeds where window.atob() fails.
73
+ *
74
+ * @param String str The base64 encoded contents
75
+ * @return string
76
+ */
77
+ this.decode = function decode(str) {
78
+ var c1, c2, c3, c4, i = 0, len = str.length, out = "";
79
+ while (i < len) {
80
+ do {
81
+ c1 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
82
+ } while (i < len && c1 === -1);
83
+ if (c1 === -1) {
84
+ break;
85
+ }
86
+ do {
87
+ c2 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
88
+ } while (i < len && c2 === -1);
89
+ if (c2 === -1) {
90
+ break;
91
+ }
92
+ out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
93
+ do {
94
+ c3 = str.charCodeAt(i++) & 0xff;
95
+ if (c3 === 61)
96
+ return out;
97
+ c3 = BASE64_DECODE_CHARS[c3];
98
+ } while (i < len && c3 === -1);
99
+ if (c3 === -1) {
100
+ break;
101
+ }
102
+ out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
103
+ do {
104
+ c4 = str.charCodeAt(i++) & 0xff;
105
+ if (c4 === 61) {
106
+ return out;
107
+ }
108
+ c4 = BASE64_DECODE_CHARS[c4];
109
+ } while (i < len && c4 === -1);
110
+ if (c4 === -1) {
111
+ break;
112
+ }
113
+ out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
114
+ }
115
+ return out;
116
+ };
117
+
118
+ /**
119
+ * Base64 encodes a string, even binary ones. Succeeds where
120
+ * window.btoa() fails.
121
+ *
122
+ * @param String str The string content to encode
123
+ * @return string
124
+ */
125
+ this.encode = function encode(str) {
126
+ var out = "", i = 0, len = str.length, c1, c2, c3;
127
+ while (i < len) {
128
+ c1 = str.charCodeAt(i++) & 0xff;
129
+ if (i === len) {
130
+ out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
131
+ out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4);
132
+ out += "==";
133
+ break;
134
+ }
135
+ c2 = str.charCodeAt(i++);
136
+ if (i === len) {
137
+ out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
138
+ out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
139
+ out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2);
140
+ out += "=";
141
+ break;
142
+ }
143
+ c3 = str.charCodeAt(i++);
144
+ out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
145
+ out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
146
+ out += BASE64_ENCODE_CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
147
+ out += BASE64_ENCODE_CHARS.charAt(c3 & 0x3F);
148
+ }
149
+ return out;
150
+ };
151
+
152
+ /**
153
+ * Checks if a given DOM element exists in remote page.
154
+ *
155
+ * @param String selector CSS3 selector
156
+ * @return Boolean
157
+ */
158
+ this.exists = function exists(selector) {
159
+ try {
160
+ return this.findAll(selector).length > 0;
161
+ } catch (e) {
162
+ return false;
163
+ }
164
+ };
165
+
166
+ /**
167
+ * Fetches innerText within the element(s) matching a given CSS3
168
+ * selector.
169
+ *
170
+ * @param String selector A CSS3 selector
171
+ * @return String
172
+ */
173
+ this.fetchText = function fetchText(selector) {
174
+ var text = '', elements = this.findAll(selector);
175
+ if (elements && elements.length) {
176
+ Array.prototype.forEach.call(elements, function _forEach(element) {
177
+ text += element.textContent || element.innerText;
178
+ });
179
+ }
180
+ return text;
181
+ };
182
+
183
+ /**
184
+ * Fills a form with provided field values, and optionnaly submits it.
185
+ *
186
+ * @param HTMLElement|String form A form element, or a CSS3 selector to a form element
187
+ * @param Object vals Field values
188
+ * @return Object An object containing setting result for each field, including file uploads
189
+ */
190
+ this.fill = function fill(form, vals) {
191
+ var out = {
192
+ errors: [],
193
+ fields: [],
194
+ files: []
195
+ };
196
+ if (!(form instanceof HTMLElement) || typeof form === "string") {
197
+ this.log("attempting to fetch form element from selector: '" + form + "'", "info");
198
+ try {
199
+ form = this.findOne(form);
200
+ } catch (e) {
201
+ if (e.name === "SYNTAX_ERR") {
202
+ out.errors.push("invalid form selector provided: '" + form + "'");
203
+ return out;
204
+ }
205
+ }
206
+ }
207
+ if (!form) {
208
+ out.errors.push("form not found");
209
+ return out;
210
+ }
211
+ for (var name in vals) {
212
+ if (!vals.hasOwnProperty(name)) {
213
+ continue;
214
+ }
215
+ var field = this.findAll('[name="' + name + '"]', form);
216
+ var value = vals[name];
217
+ if (!field) {
218
+ out.errors.push('no field named "' + name + '" in form');
219
+ continue;
220
+ }
221
+ try {
222
+ out.fields[name] = this.setField(field, value);
223
+ } catch (err) {
224
+ if (err.name === "FileUploadError") {
225
+ out.files.push({
226
+ name: name,
227
+ path: err.path
228
+ });
229
+ } else {
230
+ this.log(err, "error");
231
+ throw err;
232
+ }
233
+ }
234
+ }
235
+ return out;
236
+ };
237
+
238
+ /**
239
+ * Finds all DOM elements matching by the provided selector.
240
+ *
241
+ * @param String selector CSS3 selector
242
+ * @param HTMLElement|null scope Element to search child elements within
243
+ * @return NodeList|undefined
244
+ */
245
+ this.findAll = function findAll(selector, scope) {
246
+ scope = scope || document;
247
+ try {
248
+ var pSelector = this.processSelector(selector);
249
+ if (pSelector.type === 'xpath') {
250
+ return this.getElementsByXPath(pSelector.path);
251
+ } else {
252
+ return scope.querySelectorAll(pSelector.path);
253
+ }
254
+ } catch (e) {
255
+ this.log('findAll(): invalid selector provided "' + selector + '":' + e, "error");
256
+ }
257
+ };
258
+
259
+ /**
260
+ * Finds a DOM element by the provided selector.
261
+ *
262
+ * @param String selector CSS3 selector
263
+ * @param HTMLElement|null scope Element to search child elements within
264
+ * @return HTMLElement|undefined
265
+ */
266
+ this.findOne = function findOne(selector, scope) {
267
+ scope = scope || document;
268
+ try {
269
+ var pSelector = this.processSelector(selector);
270
+ if (pSelector.type === 'xpath') {
271
+ return this.getElementByXPath(pSelector.path);
272
+ } else {
273
+ return scope.querySelector(pSelector.path);
274
+ }
275
+ } catch (e) {
276
+ this.log('findOne(): invalid selector provided "' + selector + '":' + e, "error");
277
+ }
278
+ };
279
+
280
+ /**
281
+ * Downloads a resource behind an url and returns its base64-encoded
282
+ * contents.
283
+ *
284
+ * @param String url The resource url
285
+ * @param String method The request method, optional (default: GET)
286
+ * @param Object data The request data, optional
287
+ * @return String Base64 contents string
288
+ */
289
+ this.getBase64 = function getBase64(url, method, data) {
290
+ return this.encode(this.getBinary(url, method, data));
291
+ };
292
+
293
+ /**
294
+ * Retrieves string contents from a binary file behind an url. Silently
295
+ * fails but log errors.
296
+ *
297
+ * @param String url
298
+ * @param String method
299
+ * @param Object data
300
+ * @return string
301
+ */
302
+ this.getBinary = function getBinary(url, method, data) {
303
+ try {
304
+ var xhr = new XMLHttpRequest(), dataString = "";
305
+ if (typeof method !== "string" || ["GET", "POST"].indexOf(method.toUpperCase()) === -1) {
306
+ method = "GET";
307
+ } else {
308
+ method = method.toUpperCase();
309
+ }
310
+ xhr.open(method, url, false);
311
+ this.log("getBinary(): Using HTTP method: '" + method + "'", "debug");
312
+ xhr.overrideMimeType("text/plain; charset=x-user-defined");
313
+ if (method === "POST") {
314
+ if (typeof data === "object") {
315
+ var dataList = [];
316
+ for (var k in data) {
317
+ dataList.push(escape(k) + "=" + escape(data[k].toString()));
318
+ }
319
+ dataString = dataList.join('&');
320
+ this.log("getBinary(): Using request data: '" + dataString + "'", "debug");
321
+ } else if (typeof data === "string") {
322
+ dataString = data;
323
+ }
324
+ xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
325
+ }
326
+ xhr.send(method === "POST" ? dataString : null);
327
+ return xhr.responseText;
328
+ } catch (e) {
329
+ if (e.name === "NETWORK_ERR" && e.code === 101) {
330
+ this.log("getBinary(): Unfortunately, casperjs cannot make cross domain ajax requests", "warning");
331
+ }
332
+ this.log("getBinary(): Error while fetching " + url + ": " + e, "error");
333
+ return "";
334
+ }
335
+ };
336
+
337
+ /**
338
+ * Retrieves bounding rect coordinates of the HTML element matching the
339
+ * provided CSS3 selector
340
+ *
341
+ * @param String selector
342
+ * @return Object or null
343
+ */
344
+ this.getElementBounds = function getElementBounds(selector) {
345
+ try {
346
+ var clipRect = this.findOne(selector).getBoundingClientRect();
347
+ return {
348
+ top: clipRect.top,
349
+ left: clipRect.left,
350
+ width: clipRect.width,
351
+ height: clipRect.height
352
+ };
353
+ } catch (e) {
354
+ this.log("Unable to fetch bounds for element " + selector, "warning");
355
+ }
356
+ };
357
+
358
+ /**
359
+ * Retrieves a single DOM element matching a given XPath expression.
360
+ *
361
+ * @param String expression The XPath expression
362
+ * @return HTMLElement or null
363
+ */
364
+ this.getElementByXPath = function getElementByXPath(expression) {
365
+ var a = document.evaluate(expression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
366
+ if (a.snapshotLength > 0) {
367
+ return a.snapshotItem(0);
368
+ }
369
+ };
370
+
371
+ /**
372
+ * Retrieves all DOM elements matching a given XPath expression.
373
+ *
374
+ * @param String expression The XPath expression
375
+ * @return Array
376
+ */
377
+ this.getElementsByXPath = function getElementsByXPath(expression) {
378
+ var nodes = [];
379
+ var a = document.evaluate(expression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
380
+ for (var i = 0; i < a.snapshotLength; i++) {
381
+ nodes.push(a.snapshotItem(i));
382
+ }
383
+ return nodes;
384
+ };
385
+
386
+ /**
387
+ * Logs a message. Will format the message a way CasperJS will be able
388
+ * to log phantomjs side.
389
+ *
390
+ * @param String message The message to log
391
+ * @param String level The log level
392
+ */
393
+ this.log = function log(message, level) {
394
+ console.log("[casper:" + (level || "debug") + "] " + message);
395
+ };
396
+
397
+ /**
398
+ * Dispatches a mouse event to the DOM element behind the provided selector.
399
+ *
400
+ * @param String type Type of event to dispatch
401
+ * @param String selector A CSS3 selector to the element to click
402
+ * @return Boolean
403
+ */
404
+ this.mouseEvent = function mouseEvent(type, selector) {
405
+ var elem = this.findOne(selector);
406
+ if (!elem) {
407
+ this.log("mouseEvent(): Couldn't find any element matching '" + selector + "' selector", "error");
408
+ return false;
409
+ }
410
+ var evt = document.createEvent("MouseEvents");
411
+ evt.initMouseEvent(type, true, true, window, 1, 1, 1, 1, 1, false, false, false, false, 0, elem);
412
+ // dispatchEvent return value is false if at least one of the event
413
+ // handlers which handled this event called preventDefault
414
+ return elem.dispatchEvent(evt);
415
+ };
416
+
417
+ /**
418
+ * Processes a selector input, either as a string or an object.
419
+ *
420
+ * If passed an object, if must be of the form:
421
+ *
422
+ * selectorObject = {
423
+ * type: <'css' or 'xpath'>,
424
+ * path: <a string>
425
+ * }
426
+ *
427
+ * @param String|Object selector The selector string or object
428
+ *
429
+ * @return an object containing 'type' and 'path' keys
430
+ */
431
+ this.processSelector = function processSelector(selector) {
432
+ var selectorObject = {
433
+ toString: function toString() {
434
+ return this.type + ' selector: ' + this.path;
435
+ }
436
+ };
437
+ if (typeof selector === "string") {
438
+ // defaults to CSS selector
439
+ selectorObject.type = "css";
440
+ selectorObject.path = selector;
441
+ return selectorObject;
442
+ } else if (typeof selector === "object") {
443
+ // validation
444
+ if (!selector.hasOwnProperty('type') || !selector.hasOwnProperty('path')) {
445
+ throw new Error("Incomplete selector object");
446
+ } else if (SUPPORTED_SELECTOR_TYPES.indexOf(selector.type) === -1) {
447
+ throw new Error("Unsupported selector type: " + selector.type);
448
+ }
449
+ if (!selector.hasOwnProperty('toString')) {
450
+ selector.toString = selectorObject.toString;
451
+ }
452
+ return selector;
453
+ }
454
+ throw new Error("Unsupported selector type: " + typeof selector);
455
+ };
456
+
457
+ /**
458
+ * Removes all DOM elements matching a given XPath expression.
459
+ *
460
+ * @param String expression The XPath expression
461
+ * @return Array
462
+ */
463
+ this.removeElementsByXPath = function removeElementsByXPath(expression) {
464
+ var a = document.evaluate(expression, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
465
+ for (var i = 0; i < a.snapshotLength; i++) {
466
+ a.snapshotItem(i).parentNode.removeChild(a.snapshotItem(i));
467
+ }
468
+ };
469
+
470
+ /**
471
+ * Sets a field (or a set of fields) value. Fails silently, but log
472
+ * error messages.
473
+ *
474
+ * @param HTMLElement|NodeList field One or more element defining a field
475
+ * @param mixed value The field value to set
476
+ */
477
+ this.setField = function setField(field, value) {
478
+ var logValue, fields, out;
479
+ value = logValue = (value || "");
480
+ if (field instanceof NodeList) {
481
+ fields = field;
482
+ field = fields[0];
483
+ }
484
+ if (!field instanceof HTMLElement) {
485
+ this.log("Invalid field type; only HTMLElement and NodeList are supported", "error");
486
+ }
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");
492
+ try {
493
+ field.focus();
494
+ } catch (e) {
495
+ this.log("Unable to focus() input field " + field.getAttribute('name') + ": " + e, "warning");
496
+ }
497
+ var nodeName = field.nodeName.toLowerCase();
498
+ switch (nodeName) {
499
+ case "input":
500
+ var type = field.getAttribute('type') || "text";
501
+ switch (type.toLowerCase()) {
502
+ case "color":
503
+ case "date":
504
+ case "datetime":
505
+ case "datetime-local":
506
+ case "email":
507
+ case "hidden":
508
+ case "month":
509
+ case "number":
510
+ case "password":
511
+ case "range":
512
+ case "search":
513
+ case "tel":
514
+ case "text":
515
+ case "time":
516
+ case "url":
517
+ case "week":
518
+ field.value = value;
519
+ break;
520
+ case "checkbox":
521
+ if (fields.length > 1) {
522
+ var values = value;
523
+ if (!Array.isArray(values)) {
524
+ values = [values];
525
+ }
526
+ Array.prototype.forEach.call(fields, function _forEach(f) {
527
+ f.checked = values.indexOf(f.value) !== -1 ? true : false;
528
+ });
529
+ } else {
530
+ field.checked = value ? true : false;
531
+ }
532
+ break;
533
+ case "file":
534
+ throw {
535
+ name: "FileUploadError",
536
+ message: "File field must be filled using page.uploadFile",
537
+ path: value
538
+ };
539
+ case "radio":
540
+ if (fields) {
541
+ Array.prototype.forEach.call(fields, function _forEach(e) {
542
+ e.checked = (e.value === value);
543
+ });
544
+ } else {
545
+ out = 'Urovided radio elements are empty';
546
+ }
547
+ break;
548
+ default:
549
+ out = "Unsupported input field type: " + type;
550
+ break;
551
+ }
552
+ break;
553
+ case "select":
554
+ case "textarea":
555
+ field.value = value;
556
+ break;
557
+ default:
558
+ out = 'Unsupported field type: ' + nodeName;
559
+ break;
560
+ }
561
+ // firing the `change` event
562
+ var changeEvent = document.createEvent("HTMLEvents");
563
+ changeEvent.initEvent('change', true, true);
564
+ field.dispatchEvent(changeEvent);
565
+ // blur the field
566
+ try {
567
+ field.blur();
568
+ } catch (err) {
569
+ this.log("Unable to blur() input field " + field.getAttribute('name') + ": " + err, "warning");
570
+ }
571
+ return out;
572
+ };
573
+
574
+ /**
575
+ * Checks if a given DOM element is visible in remote page.
576
+ *
577
+ * @param String selector CSS3 selector
578
+ * @return Boolean
579
+ */
580
+ this.visible = function visible(selector) {
581
+ try {
582
+ var comp,
583
+ el = this.findOne(selector);
584
+
585
+ if (el) {
586
+ comp = window.getComputedStyle(el, null);
587
+ return comp.visibility !== 'hidden' && comp.display !== 'none' && el.offsetHeight > 0 && el.offsetWidth > 0;
588
+ }
589
+ return false;
590
+ } catch (e) {
591
+ return false;
592
+ }
593
+ };
594
+ };
595
+ })(typeof exports === "object" ? exports : window);