phantomjs-binaries 1.8.0

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