/*! * Casper is a navigation utility for PhantomJS. * * Documentation: http://casperjs.org/ * Repository: http://github.com/casperjs/casperjs * * Copyright (c) 2011-2012 Nicolas Perriault * * Part of source code is Copyright Joyent, Inc. and other Node contributors. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*global escape, NodeList*/ (function(exports) { "use strict"; exports.create = function create(options) { return new this.ClientUtils(options); }; /** * Casper client-side helpers. */ exports.ClientUtils = function ClientUtils(options) { /*eslint max-statements:0, no-multi-spaces:0*/ // private members var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var BASE64_DECODE_CHARS = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 ]; var SUPPORTED_SELECTOR_TYPES = ['css', 'xpath']; var XPATH_NAMESPACE = { svg: 'http://www.w3.org/2000/svg', mathml: 'http://www.w3.org/1998/Math/MathML' }; function form_urlencoded(str) { return encodeURIComponent(str) .replace(/%20/g, '+') .replace(/[!'()*]/g, function(c) { return '%' + c.charCodeAt(0).toString(16); }); } // public members this.options = options || {}; this.options.scope = this.options.scope || document; /** * Calls a method part of the current prototype, with arguments. * * @param {String} method Method name * @param {Array} args arguments * @return {Mixed} */ this.__call = function __call(method, args) { if (method === "__call") { return; } try { return this[method].apply(this, args); } catch (err) { err.__isCallError = true; return err; } }; /** * Clicks on the DOM element behind the provided selector. * * @param String selector A CSS3 selector to the element to click * @param {Number} x X position * @param {Number} y Y position * @return Boolean */ this.click = function click(selector, x, y) { return this.mouseEvent('click', selector, x, y); }; /** * Decodes a base64 encoded string. Succeeds where window.atob() fails. * * @param String str The base64 encoded contents * @return string */ this.decode = function decode(str) { /*eslint max-statements:0, complexity:0 */ var c1, c2, c3, c4, i = 0, len = str.length, out = ""; while (i < len) { do { c1 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff]; } while (i < len && c1 === -1); if (c1 === -1) { break; } do { c2 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff]; } while (i < len && c2 === -1); if (c2 === -1) { break; } out += String.fromCharCode(c1 << 2 | (c2 & 0x30) >> 4); do { c3 = str.charCodeAt(i++) & 0xff; if (c3 === 61) { return out; } c3 = BASE64_DECODE_CHARS[c3]; } while (i < len && c3 === -1); if (c3 === -1) { break; } out += String.fromCharCode((c2 & 0XF) << 4 | (c3 & 0x3C) >> 2); do { c4 = str.charCodeAt(i++) & 0xff; if (c4 === 61) { return out; } c4 = BASE64_DECODE_CHARS[c4]; } while (i < len && c4 === -1); if (c4 === -1) { break; } out += String.fromCharCode((c3 & 0x03) << 6 | c4); } return out; }; /** * Echoes something to casper console. * * @param String message * @return */ this.echo = function echo(message) { console.log("[casper.echo] " + message); }; /** * Checks if a given DOM element is visible in remote page. * * @param Object element DOM element * @return Boolean */ this.elementVisible = function elementVisible(elem) { var style; try { style = window.getComputedStyle(elem, null); } catch (e) { return false; } if(style.visibility === 'hidden' || style.display === 'none') return false; var cr = elem.getBoundingClientRect(); return cr.width > 0 && cr.height > 0; }; /** * Base64 encodes a string, even binary ones. Succeeds where * window.btoa() fails. * * @param String str The string content to encode * @return string */ this.encode = function encode(str) { /*eslint max-statements:0 */ var out = "", i = 0, len = str.length, c1, c2, c3; while (i < len) { c1 = str.charCodeAt(i++) & 0xff; if (i === len) { out += BASE64_ENCODE_CHARS.charAt(c1 >> 2); out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4); out += "=="; break; } c2 = str.charCodeAt(i++); if (i === len) { out += BASE64_ENCODE_CHARS.charAt(c1 >> 2); out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4 | (c2 & 0xF0) >> 4); out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2); out += "="; break; } c3 = str.charCodeAt(i++); out += BASE64_ENCODE_CHARS.charAt(c1 >> 2); out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4 | (c2 & 0xF0) >> 4); out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2 | (c3 & 0xC0) >> 6); out += BASE64_ENCODE_CHARS.charAt(c3 & 0x3F); } return out; }; /** * Checks if a given DOM element exists in remote page. * * @param String selector CSS3 selector * @return Boolean */ this.exists = function exists(selector) { try { return this.findAll(selector).length > 0; } catch (e) { return false; } }; /** * Fetches innerText within the element(s) matching a given CSS3 * selector. * * @param String selector A CSS3 selector * @return String */ this.fetchText = function fetchText(selector) { var text = '', elements = this.findAll(selector); if (elements && elements.length) { Array.prototype.forEach.call(elements, function _forEach(element) { text += element.textContent || element.innerText || element.value || ''; }); } return text; }; /** * Fills a form with provided field values, and optionally submits it. * * @param HTMLElement|String form A form element, or a CSS3 selector to a form element * @param Object vals Field values * @param String findType Element finder type (css, names, xpath, labels) * @return Object An object containing setting result for each field, including file uploads */ this.fill = function fill(form, vals, findType) { findType = findType || "names"; /*eslint complexity:0*/ var out = { errors: [], fields: [], files: [] }; if (!(form instanceof HTMLElement) || typeof form === "string") { this.log("attempting to fetch form element from selector: '" + form + "'", "info"); try { form = this.findOne(form); } catch (e) { if (e.name === "SYNTAX_ERR") { out.errors.push("invalid form selector provided: '" + form + "'"); return out; } } } if (!form) { out.errors.push("form not found"); return out; } for (var fieldSelector in vals) { if (!vals.hasOwnProperty(fieldSelector)) { continue; } try { out.fields[fieldSelector] = this.setFieldValue(this.makeSelector(fieldSelector, findType), vals[fieldSelector], form); } catch (err) { switch (err.name) { case "FieldNotFound": out.errors.push('Unable to find field element in form: ' + err.toString()); break; case "FileUploadError": out.files.push({ type: findType, selector: findType === "labels" ? '#' + err.id : fieldSelector, path: err.path }); break; default: out.errors.push(err.toString()); } } } return out; }; /** * Finds all DOM elements matching by the provided selector. * * @param String | Object selector CSS3 selector (String only) or XPath object * @param HTMLElement|null scope Element to search child elements within * @return Array|undefined */ this.findAll = function findAll(selector, scope) { scope = scope instanceof HTMLElement ? scope : scope && this.findOne(scope) || this.options.scope; try { var pSelector = this.processSelector(selector); if (pSelector.type === 'xpath') { return this.getElementsByXPath(pSelector.path, scope); } else { return Array.prototype.slice.call(scope.querySelectorAll(pSelector.path)); } } catch (e) { this.log('findAll(): invalid selector provided "' + selector + '":' + e, "error"); } }; /** * Finds a DOM element by the provided selector. * * @param String | Object selector CSS3 selector (String only) or XPath object * @param HTMLElement|null scope Element to search child elements within * @return HTMLElement|undefined */ this.findOne = function findOne(selector, scope) { scope = scope instanceof HTMLElement ? scope : scope && this.findOne(scope) || this.options.scope; try { var pSelector = this.processSelector(selector); if (pSelector.type === 'xpath') { return this.getElementByXPath(pSelector.path, scope); } else { return scope.querySelector(pSelector.path); } } catch (e) { this.log('findOne(): invalid selector provided "' + selector + '":' + e, "error"); } }; /** * Force target on