casperjs 1.0.0.RC1

Sign up to get free protection for your applications and to get access to all the features.
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);