phantomjs-binaries 1.8.0
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.
- data/Gemfile +4 -0
- data/Gemfile.lock +18 -0
- data/Rakefile +1 -0
- data/bin/bootstrap.js +342 -0
- data/bin/casperjs +58 -0
- data/bin/phantomjs +13 -0
- data/bin/phantomjs-1.8.0-darwin-x86_64 +0 -0
- data/bin/phantomjs-1.8.0-linux-x86_64 +0 -0
- data/bin/usage.txt +11 -0
- data/lib/phantomjs-binaries/version.rb +5 -0
- data/lib/phantomjs-binaries.rb +7 -0
- data/modules/casper.js +2101 -0
- data/modules/cli.js +138 -0
- data/modules/clientutils.js +781 -0
- data/modules/colorizer.js +130 -0
- data/modules/events.js +259 -0
- data/modules/http.js +70 -0
- data/modules/mouse.js +109 -0
- data/modules/pagestack.js +166 -0
- data/modules/querystring.js +187 -0
- data/modules/tester.js +1161 -0
- data/modules/utils.js +581 -0
- data/modules/vendors/coffee-script.js +8 -0
- data/modules/xunit.js +155 -0
- data/package.json +35 -0
- data/phantomjs-binaries.gemspec +24 -0
- metadata +95 -0
@@ -0,0 +1,781 @@
|
|
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
|
+
/*jshint maxstatements:40*/
|
45
|
+
// private members
|
46
|
+
var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
47
|
+
var BASE64_DECODE_CHARS = new Array(
|
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, -1, -1, -1, -1, -1,
|
50
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
51
|
+
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
52
|
+
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
53
|
+
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
54
|
+
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
55
|
+
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
|
56
|
+
);
|
57
|
+
var SUPPORTED_SELECTOR_TYPES = ['css', 'xpath'];
|
58
|
+
|
59
|
+
// public members
|
60
|
+
this.options = options || {};
|
61
|
+
this.options.scope = this.options.scope || document;
|
62
|
+
/**
|
63
|
+
* Clicks on the DOM element behind the provided selector.
|
64
|
+
*
|
65
|
+
* @param String selector A CSS3 selector to the element to click
|
66
|
+
* @return Boolean
|
67
|
+
*/
|
68
|
+
this.click = function click(selector) {
|
69
|
+
return this.mouseEvent('click', selector);
|
70
|
+
};
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Decodes a base64 encoded string. Succeeds where window.atob() fails.
|
74
|
+
*
|
75
|
+
* @param String str The base64 encoded contents
|
76
|
+
* @return string
|
77
|
+
*/
|
78
|
+
this.decode = function decode(str) {
|
79
|
+
/*jshint maxstatements:30 maxcomplexity:30 */
|
80
|
+
var c1, c2, c3, c4, i = 0, len = str.length, out = "";
|
81
|
+
while (i < len) {
|
82
|
+
do {
|
83
|
+
c1 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
|
84
|
+
} while (i < len && c1 === -1);
|
85
|
+
if (c1 === -1) {
|
86
|
+
break;
|
87
|
+
}
|
88
|
+
do {
|
89
|
+
c2 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
|
90
|
+
} while (i < len && c2 === -1);
|
91
|
+
if (c2 === -1) {
|
92
|
+
break;
|
93
|
+
}
|
94
|
+
out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
|
95
|
+
do {
|
96
|
+
c3 = str.charCodeAt(i++) & 0xff;
|
97
|
+
if (c3 === 61)
|
98
|
+
return out;
|
99
|
+
c3 = BASE64_DECODE_CHARS[c3];
|
100
|
+
} while (i < len && c3 === -1);
|
101
|
+
if (c3 === -1) {
|
102
|
+
break;
|
103
|
+
}
|
104
|
+
out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
|
105
|
+
do {
|
106
|
+
c4 = str.charCodeAt(i++) & 0xff;
|
107
|
+
if (c4 === 61) {
|
108
|
+
return out;
|
109
|
+
}
|
110
|
+
c4 = BASE64_DECODE_CHARS[c4];
|
111
|
+
} while (i < len && c4 === -1);
|
112
|
+
if (c4 === -1) {
|
113
|
+
break;
|
114
|
+
}
|
115
|
+
out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
|
116
|
+
}
|
117
|
+
return out;
|
118
|
+
};
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Echoes something to casper console.
|
122
|
+
*
|
123
|
+
* @param String message
|
124
|
+
* @return
|
125
|
+
*/
|
126
|
+
this.echo = function echo(message) {
|
127
|
+
console.log("[casper.echo] " + message);
|
128
|
+
};
|
129
|
+
|
130
|
+
/**
|
131
|
+
* Base64 encodes a string, even binary ones. Succeeds where
|
132
|
+
* window.btoa() fails.
|
133
|
+
*
|
134
|
+
* @param String str The string content to encode
|
135
|
+
* @return string
|
136
|
+
*/
|
137
|
+
this.encode = function encode(str) {
|
138
|
+
/*jshint maxstatements:30 */
|
139
|
+
var out = "", i = 0, len = str.length, c1, c2, c3;
|
140
|
+
while (i < len) {
|
141
|
+
c1 = str.charCodeAt(i++) & 0xff;
|
142
|
+
if (i === len) {
|
143
|
+
out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
|
144
|
+
out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4);
|
145
|
+
out += "==";
|
146
|
+
break;
|
147
|
+
}
|
148
|
+
c2 = str.charCodeAt(i++);
|
149
|
+
if (i === len) {
|
150
|
+
out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
|
151
|
+
out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
|
152
|
+
out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2);
|
153
|
+
out += "=";
|
154
|
+
break;
|
155
|
+
}
|
156
|
+
c3 = str.charCodeAt(i++);
|
157
|
+
out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
|
158
|
+
out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
|
159
|
+
out += BASE64_ENCODE_CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
|
160
|
+
out += BASE64_ENCODE_CHARS.charAt(c3 & 0x3F);
|
161
|
+
}
|
162
|
+
return out;
|
163
|
+
};
|
164
|
+
|
165
|
+
/**
|
166
|
+
* Checks if a given DOM element exists in remote page.
|
167
|
+
*
|
168
|
+
* @param String selector CSS3 selector
|
169
|
+
* @return Boolean
|
170
|
+
*/
|
171
|
+
this.exists = function exists(selector) {
|
172
|
+
try {
|
173
|
+
return this.findAll(selector).length > 0;
|
174
|
+
} catch (e) {
|
175
|
+
return false;
|
176
|
+
}
|
177
|
+
};
|
178
|
+
|
179
|
+
/**
|
180
|
+
* Fetches innerText within the element(s) matching a given CSS3
|
181
|
+
* selector.
|
182
|
+
*
|
183
|
+
* @param String selector A CSS3 selector
|
184
|
+
* @return String
|
185
|
+
*/
|
186
|
+
this.fetchText = function fetchText(selector) {
|
187
|
+
var text = '', elements = this.findAll(selector);
|
188
|
+
if (elements && elements.length) {
|
189
|
+
Array.prototype.forEach.call(elements, function _forEach(element) {
|
190
|
+
text += element.textContent || element.innerText;
|
191
|
+
});
|
192
|
+
}
|
193
|
+
return text;
|
194
|
+
};
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Fills a form with provided field values, and optionnaly submits it.
|
198
|
+
*
|
199
|
+
* @param HTMLElement|String form A form element, or a CSS3 selector to a form element
|
200
|
+
* @param Object vals Field values
|
201
|
+
* @return Object An object containing setting result for each field, including file uploads
|
202
|
+
*/
|
203
|
+
this.fill = function fill(form, vals) {
|
204
|
+
/*jshint maxcomplexity:8*/
|
205
|
+
var out = {
|
206
|
+
errors: [],
|
207
|
+
fields: [],
|
208
|
+
files: []
|
209
|
+
};
|
210
|
+
if (!(form instanceof HTMLElement) || typeof form === "string") {
|
211
|
+
this.log("attempting to fetch form element from selector: '" + form + "'", "info");
|
212
|
+
try {
|
213
|
+
form = this.findOne(form);
|
214
|
+
} catch (e) {
|
215
|
+
if (e.name === "SYNTAX_ERR") {
|
216
|
+
out.errors.push("invalid form selector provided: '" + form + "'");
|
217
|
+
return out;
|
218
|
+
}
|
219
|
+
}
|
220
|
+
}
|
221
|
+
if (!form) {
|
222
|
+
out.errors.push("form not found");
|
223
|
+
return out;
|
224
|
+
}
|
225
|
+
for (var name in vals) {
|
226
|
+
if (!vals.hasOwnProperty(name)) {
|
227
|
+
continue;
|
228
|
+
}
|
229
|
+
var field = this.findAll('[name="' + name + '"]', form);
|
230
|
+
var value = vals[name];
|
231
|
+
if (!field || field.length === 0) {
|
232
|
+
out.errors.push('no field named "' + name + '" in form');
|
233
|
+
continue;
|
234
|
+
}
|
235
|
+
try {
|
236
|
+
out.fields[name] = this.setField(field, value);
|
237
|
+
} catch (err) {
|
238
|
+
if (err.name === "FileUploadError") {
|
239
|
+
out.files.push({
|
240
|
+
name: name,
|
241
|
+
path: err.path
|
242
|
+
});
|
243
|
+
} else if(err.name === "FieldNotFound") {
|
244
|
+
out.errors.push('Form field named "' + name + '" was not found.');
|
245
|
+
} else {
|
246
|
+
out.errors.push(err.toString());
|
247
|
+
}
|
248
|
+
}
|
249
|
+
}
|
250
|
+
return out;
|
251
|
+
};
|
252
|
+
|
253
|
+
/**
|
254
|
+
* Finds all DOM elements matching by the provided selector.
|
255
|
+
*
|
256
|
+
* @param String selector CSS3 selector
|
257
|
+
* @param HTMLElement|null scope Element to search child elements within
|
258
|
+
* @return NodeList|undefined
|
259
|
+
*/
|
260
|
+
this.findAll = function findAll(selector, scope) {
|
261
|
+
scope = scope || this.options.scope;
|
262
|
+
try {
|
263
|
+
var pSelector = this.processSelector(selector);
|
264
|
+
if (pSelector.type === 'xpath') {
|
265
|
+
return this.getElementsByXPath(pSelector.path, scope);
|
266
|
+
} else {
|
267
|
+
return scope.querySelectorAll(pSelector.path);
|
268
|
+
}
|
269
|
+
} catch (e) {
|
270
|
+
this.log('findAll(): invalid selector provided "' + selector + '":' + e, "error");
|
271
|
+
}
|
272
|
+
};
|
273
|
+
|
274
|
+
/**
|
275
|
+
* Finds a DOM element by the provided selector.
|
276
|
+
*
|
277
|
+
* @param String selector CSS3 selector
|
278
|
+
* @param HTMLElement|null scope Element to search child elements within
|
279
|
+
* @return HTMLElement|undefined
|
280
|
+
*/
|
281
|
+
this.findOne = function findOne(selector, scope) {
|
282
|
+
scope = scope || this.options.scope;
|
283
|
+
try {
|
284
|
+
var pSelector = this.processSelector(selector);
|
285
|
+
if (pSelector.type === 'xpath') {
|
286
|
+
return this.getElementByXPath(pSelector.path, scope);
|
287
|
+
} else {
|
288
|
+
return scope.querySelector(pSelector.path);
|
289
|
+
}
|
290
|
+
} catch (e) {
|
291
|
+
this.log('findOne(): invalid selector provided "' + selector + '":' + e, "error");
|
292
|
+
}
|
293
|
+
};
|
294
|
+
|
295
|
+
/**
|
296
|
+
* Downloads a resource behind an url and returns its base64-encoded
|
297
|
+
* contents.
|
298
|
+
*
|
299
|
+
* @param String url The resource url
|
300
|
+
* @param String method The request method, optional (default: GET)
|
301
|
+
* @param Object data The request data, optional
|
302
|
+
* @return String Base64 contents string
|
303
|
+
*/
|
304
|
+
this.getBase64 = function getBase64(url, method, data) {
|
305
|
+
return this.encode(this.getBinary(url, method, data));
|
306
|
+
};
|
307
|
+
|
308
|
+
/**
|
309
|
+
* Retrieves string contents from a binary file behind an url. Silently
|
310
|
+
* fails but log errors.
|
311
|
+
*
|
312
|
+
* @param String url Url.
|
313
|
+
* @param String method HTTP method.
|
314
|
+
* @param Object data Request parameters.
|
315
|
+
* @return String
|
316
|
+
*/
|
317
|
+
this.getBinary = function getBinary(url, method, data) {
|
318
|
+
try {
|
319
|
+
return this.sendAJAX(url, method, data, false);
|
320
|
+
} catch (e) {
|
321
|
+
if (e.name === "NETWORK_ERR" && e.code === 101) {
|
322
|
+
this.log("getBinary(): Unfortunately, casperjs cannot make cross domain ajax requests", "warning");
|
323
|
+
}
|
324
|
+
this.log("getBinary(): Error while fetching " + url + ": " + e, "error");
|
325
|
+
return "";
|
326
|
+
}
|
327
|
+
};
|
328
|
+
|
329
|
+
/**
|
330
|
+
* Retrieves total document height.
|
331
|
+
* http://james.padolsey.com/javascript/get-document-height-cross-browser/
|
332
|
+
*
|
333
|
+
* @return {Number}
|
334
|
+
*/
|
335
|
+
this.getDocumentHeight = function getDocumentHeight() {
|
336
|
+
return Math.max(
|
337
|
+
Math.max(document.body.scrollHeight, document.documentElement.scrollHeight),
|
338
|
+
Math.max(document.body.offsetHeight, document.documentElement.offsetHeight),
|
339
|
+
Math.max(document.body.clientHeight, document.documentElement.clientHeight)
|
340
|
+
);
|
341
|
+
};
|
342
|
+
|
343
|
+
/**
|
344
|
+
* Retrieves bounding rect coordinates of the HTML element matching the
|
345
|
+
* provided CSS3 selector in the following form:
|
346
|
+
*
|
347
|
+
* {top: y, left: x, width: w, height:, h}
|
348
|
+
*
|
349
|
+
* @param String selector
|
350
|
+
* @return Object or null
|
351
|
+
*/
|
352
|
+
this.getElementBounds = function getElementBounds(selector) {
|
353
|
+
try {
|
354
|
+
var clipRect = this.findOne(selector).getBoundingClientRect();
|
355
|
+
return {
|
356
|
+
top: clipRect.top,
|
357
|
+
left: clipRect.left,
|
358
|
+
width: clipRect.width,
|
359
|
+
height: clipRect.height
|
360
|
+
};
|
361
|
+
} catch (e) {
|
362
|
+
this.log("Unable to fetch bounds for element " + selector, "warning");
|
363
|
+
}
|
364
|
+
};
|
365
|
+
|
366
|
+
/**
|
367
|
+
* Retrieves the list of bounding rect coordinates for all the HTML elements matching the
|
368
|
+
* provided CSS3 selector, in the following form:
|
369
|
+
*
|
370
|
+
* [{top: y, left: x, width: w, height:, h},
|
371
|
+
* {top: y, left: x, width: w, height:, h},
|
372
|
+
* ...]
|
373
|
+
*
|
374
|
+
* @param String selector
|
375
|
+
* @return Array
|
376
|
+
*/
|
377
|
+
this.getElementsBounds = function getElementsBounds(selector) {
|
378
|
+
var elements = this.findAll(selector);
|
379
|
+
var self = this;
|
380
|
+
try {
|
381
|
+
return Array.prototype.map.call(elements, function(element) {
|
382
|
+
var clipRect = element.getBoundingClientRect();
|
383
|
+
return {
|
384
|
+
top: clipRect.top,
|
385
|
+
left: clipRect.left,
|
386
|
+
width: clipRect.width,
|
387
|
+
height: clipRect.height
|
388
|
+
};
|
389
|
+
});
|
390
|
+
} catch (e) {
|
391
|
+
this.log("Unable to fetch bounds for elements matching " + selector, "warning");
|
392
|
+
}
|
393
|
+
};
|
394
|
+
|
395
|
+
/**
|
396
|
+
* Retrieves information about the node matching the provided selector.
|
397
|
+
*
|
398
|
+
* @param String|Object selector CSS3/XPath selector
|
399
|
+
* @return Object
|
400
|
+
*/
|
401
|
+
this.getElementInfo = function getElementInfo(selector) {
|
402
|
+
var element = this.findOne(selector);
|
403
|
+
var bounds = this.getElementBounds(selector);
|
404
|
+
var attributes = {};
|
405
|
+
[].forEach.call(element.attributes, function(attr) {
|
406
|
+
attributes[attr.name.toLowerCase()] = attr.value;
|
407
|
+
});
|
408
|
+
return {
|
409
|
+
nodeName: element.nodeName.toLowerCase(),
|
410
|
+
attributes: attributes,
|
411
|
+
tag: element.outerHTML,
|
412
|
+
html: element.innerHTML,
|
413
|
+
text: element.innerText,
|
414
|
+
x: bounds.left,
|
415
|
+
y: bounds.top,
|
416
|
+
width: bounds.width,
|
417
|
+
height: bounds.height,
|
418
|
+
visible: this.visible(selector)
|
419
|
+
};
|
420
|
+
};
|
421
|
+
|
422
|
+
/**
|
423
|
+
* Retrieves a single DOM element matching a given XPath expression.
|
424
|
+
*
|
425
|
+
* @param String expression The XPath expression
|
426
|
+
* @param HTMLElement|null scope Element to search child elements within
|
427
|
+
* @return HTMLElement or null
|
428
|
+
*/
|
429
|
+
this.getElementByXPath = function getElementByXPath(expression, scope) {
|
430
|
+
scope = scope || this.options.scope;
|
431
|
+
var a = document.evaluate(expression, scope, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
432
|
+
if (a.snapshotLength > 0) {
|
433
|
+
return a.snapshotItem(0);
|
434
|
+
}
|
435
|
+
};
|
436
|
+
|
437
|
+
/**
|
438
|
+
* Retrieves all DOM elements matching a given XPath expression.
|
439
|
+
*
|
440
|
+
* @param String expression The XPath expression
|
441
|
+
* @param HTMLElement|null scope Element to search child elements within
|
442
|
+
* @return Array
|
443
|
+
*/
|
444
|
+
this.getElementsByXPath = function getElementsByXPath(expression, scope) {
|
445
|
+
scope = scope || this.options.scope;
|
446
|
+
var nodes = [];
|
447
|
+
var a = document.evaluate(expression, scope, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
448
|
+
for (var i = 0; i < a.snapshotLength; i++) {
|
449
|
+
nodes.push(a.snapshotItem(i));
|
450
|
+
}
|
451
|
+
return nodes;
|
452
|
+
};
|
453
|
+
|
454
|
+
/**
|
455
|
+
* Retrieves the value of a form field.
|
456
|
+
*
|
457
|
+
* @param String inputName The for input name attr value
|
458
|
+
* @return Mixed
|
459
|
+
*/
|
460
|
+
this.getFieldValue = function getFieldValue(inputName) {
|
461
|
+
function getSingleValue(input) {
|
462
|
+
try {
|
463
|
+
type = input.getAttribute('type').toLowerCase();
|
464
|
+
} catch (e) {
|
465
|
+
type = 'other';
|
466
|
+
}
|
467
|
+
if (['checkbox', 'radio'].indexOf(type) === -1) {
|
468
|
+
return input.value;
|
469
|
+
}
|
470
|
+
// single checkbox or… radio button (weird, I know)
|
471
|
+
if (input.hasAttribute('value')) {
|
472
|
+
return input.checked ? input.getAttribute('value') : undefined;
|
473
|
+
}
|
474
|
+
return input.checked;
|
475
|
+
}
|
476
|
+
function getMultipleValues(inputs) {
|
477
|
+
type = inputs[0].getAttribute('type').toLowerCase();
|
478
|
+
if (type === 'radio') {
|
479
|
+
var value;
|
480
|
+
[].forEach.call(inputs, function(radio) {
|
481
|
+
value = radio.checked ? radio.value : undefined;
|
482
|
+
});
|
483
|
+
return value;
|
484
|
+
} else if (type === 'checkbox') {
|
485
|
+
var values = [];
|
486
|
+
[].forEach.call(inputs, function(checkbox) {
|
487
|
+
if (checkbox.checked) {
|
488
|
+
values.push(checkbox.value);
|
489
|
+
}
|
490
|
+
});
|
491
|
+
return values;
|
492
|
+
}
|
493
|
+
}
|
494
|
+
var inputs = this.findAll('[name="' + inputName + '"]'), type;
|
495
|
+
switch (inputs.length) {
|
496
|
+
case 0: return null;
|
497
|
+
case 1: return getSingleValue(inputs[0]);
|
498
|
+
default: return getMultipleValues(inputs);
|
499
|
+
}
|
500
|
+
};
|
501
|
+
|
502
|
+
/**
|
503
|
+
* Retrieves a given form all of its field values.
|
504
|
+
*
|
505
|
+
* @param String selector A DOM CSS3/XPath selector
|
506
|
+
* @return Object
|
507
|
+
*/
|
508
|
+
this.getFormValues = function getFormValues(selector) {
|
509
|
+
var form = this.findOne(selector);
|
510
|
+
var values = {};
|
511
|
+
var self = this;
|
512
|
+
[].forEach.call(form.elements, function(element) {
|
513
|
+
var name = element.getAttribute('name');
|
514
|
+
if (name) {
|
515
|
+
values[name] = self.getFieldValue(name);
|
516
|
+
}
|
517
|
+
});
|
518
|
+
return values;
|
519
|
+
};
|
520
|
+
|
521
|
+
/**
|
522
|
+
* Logs a message. Will format the message a way CasperJS will be able
|
523
|
+
* to log phantomjs side.
|
524
|
+
*
|
525
|
+
* @param String message The message to log
|
526
|
+
* @param String level The log level
|
527
|
+
*/
|
528
|
+
this.log = function log(message, level) {
|
529
|
+
console.log("[casper:" + (level || "debug") + "] " + message);
|
530
|
+
};
|
531
|
+
|
532
|
+
/**
|
533
|
+
* Dispatches a mouse event to the DOM element behind the provided selector.
|
534
|
+
*
|
535
|
+
* @param String type Type of event to dispatch
|
536
|
+
* @param String selector A CSS3 selector to the element to click
|
537
|
+
* @return Boolean
|
538
|
+
*/
|
539
|
+
this.mouseEvent = function mouseEvent(type, selector) {
|
540
|
+
var elem = this.findOne(selector);
|
541
|
+
if (!elem) {
|
542
|
+
this.log("mouseEvent(): Couldn't find any element matching '" + selector + "' selector", "error");
|
543
|
+
return false;
|
544
|
+
}
|
545
|
+
try {
|
546
|
+
var evt = document.createEvent("MouseEvents");
|
547
|
+
var center_x = 1, center_y = 1;
|
548
|
+
try {
|
549
|
+
var pos = elem.getBoundingClientRect();
|
550
|
+
center_x = Math.floor((pos.left + pos.right) / 2),
|
551
|
+
center_y = Math.floor((pos.top + pos.bottom) / 2);
|
552
|
+
} catch(e) {}
|
553
|
+
evt.initMouseEvent(type, true, true, window, 1, 1, 1, center_x, center_y, false, false, false, false, 0, elem);
|
554
|
+
// dispatchEvent return value is false if at least one of the event
|
555
|
+
// handlers which handled this event called preventDefault;
|
556
|
+
// so we cannot returns this results as it cannot accurately informs on the status
|
557
|
+
// of the operation
|
558
|
+
// let's assume the event has been sent ok it didn't raise any error
|
559
|
+
elem.dispatchEvent(evt);
|
560
|
+
return true;
|
561
|
+
} catch (e) {
|
562
|
+
this.log("Failed dispatching " + type + "mouse event on " + selector + ": " + e, "error");
|
563
|
+
return false;
|
564
|
+
}
|
565
|
+
};
|
566
|
+
|
567
|
+
/**
|
568
|
+
* Processes a selector input, either as a string or an object.
|
569
|
+
*
|
570
|
+
* If passed an object, if must be of the form:
|
571
|
+
*
|
572
|
+
* selectorObject = {
|
573
|
+
* type: <'css' or 'xpath'>,
|
574
|
+
* path: <a string>
|
575
|
+
* }
|
576
|
+
*
|
577
|
+
* @param String|Object selector The selector string or object
|
578
|
+
*
|
579
|
+
* @return an object containing 'type' and 'path' keys
|
580
|
+
*/
|
581
|
+
this.processSelector = function processSelector(selector) {
|
582
|
+
var selectorObject = {
|
583
|
+
toString: function toString() {
|
584
|
+
return this.type + ' selector: ' + this.path;
|
585
|
+
}
|
586
|
+
};
|
587
|
+
if (typeof selector === "string") {
|
588
|
+
// defaults to CSS selector
|
589
|
+
selectorObject.type = "css";
|
590
|
+
selectorObject.path = selector;
|
591
|
+
return selectorObject;
|
592
|
+
} else if (typeof selector === "object") {
|
593
|
+
// validation
|
594
|
+
if (!selector.hasOwnProperty('type') || !selector.hasOwnProperty('path')) {
|
595
|
+
throw new Error("Incomplete selector object");
|
596
|
+
} else if (SUPPORTED_SELECTOR_TYPES.indexOf(selector.type) === -1) {
|
597
|
+
throw new Error("Unsupported selector type: " + selector.type);
|
598
|
+
}
|
599
|
+
if (!selector.hasOwnProperty('toString')) {
|
600
|
+
selector.toString = selectorObject.toString;
|
601
|
+
}
|
602
|
+
return selector;
|
603
|
+
}
|
604
|
+
throw new Error("Unsupported selector type: " + typeof selector);
|
605
|
+
};
|
606
|
+
|
607
|
+
/**
|
608
|
+
* Removes all DOM elements matching a given XPath expression.
|
609
|
+
*
|
610
|
+
* @param String expression The XPath expression
|
611
|
+
* @return Array
|
612
|
+
*/
|
613
|
+
this.removeElementsByXPath = function removeElementsByXPath(expression) {
|
614
|
+
var a = document.evaluate(expression, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
|
615
|
+
for (var i = 0; i < a.snapshotLength; i++) {
|
616
|
+
a.snapshotItem(i).parentNode.removeChild(a.snapshotItem(i));
|
617
|
+
}
|
618
|
+
};
|
619
|
+
|
620
|
+
/**
|
621
|
+
* Performs an AJAX request.
|
622
|
+
*
|
623
|
+
* @param String url Url.
|
624
|
+
* @param String method HTTP method (default: GET).
|
625
|
+
* @param Object data Request parameters.
|
626
|
+
* @param Boolean async Asynchroneous request? (default: false)
|
627
|
+
* @return String Response text.
|
628
|
+
*/
|
629
|
+
this.sendAJAX = function sendAJAX(url, method, data, async) {
|
630
|
+
var xhr = new XMLHttpRequest(),
|
631
|
+
dataString = "",
|
632
|
+
dataList = [];
|
633
|
+
method = method && method.toUpperCase() || "GET";
|
634
|
+
xhr.open(method, url, !!async);
|
635
|
+
this.log("sendAJAX(): Using HTTP method: '" + method + "'", "debug");
|
636
|
+
xhr.overrideMimeType("text/plain; charset=x-user-defined");
|
637
|
+
if (method === "POST") {
|
638
|
+
if (typeof data === "object") {
|
639
|
+
for (var k in data) {
|
640
|
+
dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString()));
|
641
|
+
}
|
642
|
+
dataString = dataList.join('&');
|
643
|
+
this.log("sendAJAX(): Using request data: '" + dataString + "'", "debug");
|
644
|
+
} else if (typeof data === "string") {
|
645
|
+
dataString = data;
|
646
|
+
}
|
647
|
+
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
648
|
+
}
|
649
|
+
xhr.send(method === "POST" ? dataString : null);
|
650
|
+
return xhr.responseText;
|
651
|
+
};
|
652
|
+
|
653
|
+
/**
|
654
|
+
* Sets a field (or a set of fields) value. Fails silently, but log
|
655
|
+
* error messages.
|
656
|
+
*
|
657
|
+
* @param HTMLElement|NodeList field One or more element defining a field
|
658
|
+
* @param mixed value The field value to set
|
659
|
+
*/
|
660
|
+
this.setField = function setField(field, value) {
|
661
|
+
/*jshint maxcomplexity:99 */
|
662
|
+
var logValue, fields, out;
|
663
|
+
value = logValue = (value || "");
|
664
|
+
if (field instanceof NodeList) {
|
665
|
+
fields = field;
|
666
|
+
field = fields[0];
|
667
|
+
}
|
668
|
+
if (!(field instanceof HTMLElement)) {
|
669
|
+
var error = new Error('Invalid field type; only HTMLElement and NodeList are supported');
|
670
|
+
error.name = 'FieldNotFound';
|
671
|
+
throw error;
|
672
|
+
}
|
673
|
+
if (this.options && this.options.safeLogs && field.getAttribute('type') === "password") {
|
674
|
+
// obfuscate password value
|
675
|
+
logValue = new Array(value.length + 1).join("*");
|
676
|
+
}
|
677
|
+
this.log('Set "' + field.getAttribute('name') + '" field value to ' + logValue, "debug");
|
678
|
+
try {
|
679
|
+
field.focus();
|
680
|
+
} catch (e) {
|
681
|
+
this.log("Unable to focus() input field " + field.getAttribute('name') + ": " + e, "warning");
|
682
|
+
}
|
683
|
+
var nodeName = field.nodeName.toLowerCase();
|
684
|
+
switch (nodeName) {
|
685
|
+
case "input":
|
686
|
+
var type = field.getAttribute('type') || "text";
|
687
|
+
switch (type.toLowerCase()) {
|
688
|
+
case "color":
|
689
|
+
case "date":
|
690
|
+
case "datetime":
|
691
|
+
case "datetime-local":
|
692
|
+
case "email":
|
693
|
+
case "hidden":
|
694
|
+
case "month":
|
695
|
+
case "number":
|
696
|
+
case "password":
|
697
|
+
case "range":
|
698
|
+
case "search":
|
699
|
+
case "tel":
|
700
|
+
case "text":
|
701
|
+
case "time":
|
702
|
+
case "url":
|
703
|
+
case "week":
|
704
|
+
field.value = value;
|
705
|
+
break;
|
706
|
+
case "checkbox":
|
707
|
+
if (fields.length > 1) {
|
708
|
+
var values = value;
|
709
|
+
if (!Array.isArray(values)) {
|
710
|
+
values = [values];
|
711
|
+
}
|
712
|
+
Array.prototype.forEach.call(fields, function _forEach(f) {
|
713
|
+
f.checked = values.indexOf(f.value) !== -1 ? true : false;
|
714
|
+
});
|
715
|
+
} else {
|
716
|
+
field.checked = value ? true : false;
|
717
|
+
}
|
718
|
+
break;
|
719
|
+
case "file":
|
720
|
+
throw {
|
721
|
+
name: "FileUploadError",
|
722
|
+
message: "File field must be filled using page.uploadFile",
|
723
|
+
path: value
|
724
|
+
};
|
725
|
+
case "radio":
|
726
|
+
if (fields) {
|
727
|
+
Array.prototype.forEach.call(fields, function _forEach(e) {
|
728
|
+
e.checked = (e.value === value);
|
729
|
+
});
|
730
|
+
} else {
|
731
|
+
out = 'Provided radio elements are empty';
|
732
|
+
}
|
733
|
+
break;
|
734
|
+
default:
|
735
|
+
out = "Unsupported input field type: " + type;
|
736
|
+
break;
|
737
|
+
}
|
738
|
+
break;
|
739
|
+
case "select":
|
740
|
+
case "textarea":
|
741
|
+
field.value = value;
|
742
|
+
break;
|
743
|
+
default:
|
744
|
+
out = 'Unsupported field type: ' + nodeName;
|
745
|
+
break;
|
746
|
+
}
|
747
|
+
// firing the `change` event
|
748
|
+
var changeEvent = document.createEvent("HTMLEvents");
|
749
|
+
changeEvent.initEvent('change', true, true);
|
750
|
+
field.dispatchEvent(changeEvent);
|
751
|
+
// blur the field
|
752
|
+
try {
|
753
|
+
field.blur();
|
754
|
+
} catch (err) {
|
755
|
+
this.log("Unable to blur() input field " + field.getAttribute('name') + ": " + err, "warning");
|
756
|
+
}
|
757
|
+
return out;
|
758
|
+
};
|
759
|
+
|
760
|
+
/**
|
761
|
+
* Checks if a given DOM element is visible in remote page.
|
762
|
+
*
|
763
|
+
* @param String selector CSS3 selector
|
764
|
+
* @return Boolean
|
765
|
+
*/
|
766
|
+
this.visible = function visible(selector) {
|
767
|
+
try {
|
768
|
+
var comp,
|
769
|
+
el = this.findOne(selector);
|
770
|
+
|
771
|
+
if (el) {
|
772
|
+
comp = window.getComputedStyle(el, null);
|
773
|
+
return comp.visibility !== 'hidden' && comp.display !== 'none' && el.offsetHeight > 0 && el.offsetWidth > 0;
|
774
|
+
}
|
775
|
+
return false;
|
776
|
+
} catch (e) {
|
777
|
+
return false;
|
778
|
+
}
|
779
|
+
};
|
780
|
+
};
|
781
|
+
})(typeof exports === "object" ? exports : window);
|