Commit f4514d88 f4514d88a47d25f4b7654c7d1db3bf11e323c6b8 by Nicolas Perriault

added Casper.sendKeys() - refs #302

to send native keyboard events to the element matching a given selector.
1 parent de27282e
...@@ -65,6 +65,7 @@ Also, `Casper.mouseEvent()` will now directly trigger an error on failure instea ...@@ -65,6 +65,7 @@ Also, `Casper.mouseEvent()` will now directly trigger an error on failure instea
65 - fixed [#290](https://github.com/n1k0/casperjs/issues/#290) - add a simplistic RPM spec file to make it easier to (un)install casperjs 65 - fixed [#290](https://github.com/n1k0/casperjs/issues/#290) - add a simplistic RPM spec file to make it easier to (un)install casperjs
66 - fixed [`utils.betterTypeOf()`](http://casperjs.org/api.html#casper.betterTypeOf) to properly handle `undefined` and `null` values 66 - fixed [`utils.betterTypeOf()`](http://casperjs.org/api.html#casper.betterTypeOf) to properly handle `undefined` and `null` values
67 - fixed `Casper.die()` and `Casper.evaluateOrDie()` were not printing the error onto the console 67 - fixed `Casper.die()` and `Casper.evaluateOrDie()` were not printing the error onto the console
68 - added [`Casper.sendKeys()`](http://casperjs.org/api.html#casper.sendKeys) to send native keyboard events to the element matching a given selector
68 - added [`Casper.getFormValues()`](http://casperjs.org/api.html#casper.getFormValues) to check for the field values of a given form 69 - added [`Casper.getFormValues()`](http://casperjs.org/api.html#casper.getFormValues) to check for the field values of a given form
69 - added [`Tester.assertTextDoesntExist()`](http://casperjs.org/api.html#tester.assertTextDoesntExist) 70 - added [`Tester.assertTextDoesntExist()`](http://casperjs.org/api.html#tester.assertTextDoesntExist)
70 - added `Tester.assertFalse()` as an alias of `Tester.assertNot()` 71 - added `Tester.assertFalse()` as an alias of `Tester.assertNot()`
......
...@@ -221,7 +221,7 @@ Casper.prototype.back = function back() { ...@@ -221,7 +221,7 @@ Casper.prototype.back = function back() {
221 Casper.prototype.base64encode = function base64encode(url, method, data) { 221 Casper.prototype.base64encode = function base64encode(url, method, data) {
222 "use strict"; 222 "use strict";
223 return this.evaluate(function _evaluate(url, method, data) { 223 return this.evaluate(function _evaluate(url, method, data) {
224 return window.__utils__.getBase64(url, method, data); 224 return __utils__.getBase64(url, method, data);
225 }, url, method, data); 225 }, url, method, data);
226 }; 226 };
227 227
...@@ -386,7 +386,11 @@ Casper.prototype.clear = function clear() { ...@@ -386,7 +386,11 @@ Casper.prototype.clear = function clear() {
386 Casper.prototype.click = function click(selector) { 386 Casper.prototype.click = function click(selector) {
387 "use strict"; 387 "use strict";
388 this.checkStarted(); 388 this.checkStarted();
389 return this.mouseEvent('click', selector); 389 var success = this.mouseEvent('click', selector);
390 this.evaluate(function(selector) {
391 document.querySelector(selector).focus();
392 }, selector);
393 return success;
390 }; 394 };
391 395
392 /** 396 /**
...@@ -642,7 +646,7 @@ Casper.prototype.exists = function exists(selector) { ...@@ -642,7 +646,7 @@ Casper.prototype.exists = function exists(selector) {
642 "use strict"; 646 "use strict";
643 this.checkStarted(); 647 this.checkStarted();
644 return this.evaluate(function _evaluate(selector) { 648 return this.evaluate(function _evaluate(selector) {
645 return window.__utils__.exists(selector); 649 return __utils__.exists(selector);
646 }, selector); 650 }, selector);
647 }; 651 };
648 652
...@@ -669,7 +673,7 @@ Casper.prototype.fetchText = function fetchText(selector) { ...@@ -669,7 +673,7 @@ Casper.prototype.fetchText = function fetchText(selector) {
669 "use strict"; 673 "use strict";
670 this.checkStarted(); 674 this.checkStarted();
671 return this.evaluate(function _evaluate(selector) { 675 return this.evaluate(function _evaluate(selector) {
672 return window.__utils__.fetchText(selector); 676 return __utils__.fetchText(selector);
673 }, selector); 677 }, selector);
674 }; 678 };
675 679
...@@ -689,7 +693,7 @@ Casper.prototype.fill = function fill(selector, vals, submit) { ...@@ -689,7 +693,7 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
689 } 693 }
690 this.emit('fill', selector, vals, submit); 694 this.emit('fill', selector, vals, submit);
691 var fillResults = this.evaluate(function _evaluate(selector, values) { 695 var fillResults = this.evaluate(function _evaluate(selector, values) {
692 return window.__utils__.fill(selector, values); 696 return __utils__.fill(selector, values);
693 }, selector, vals); 697 }, selector, vals);
694 if (!fillResults) { 698 if (!fillResults) {
695 throw new CasperError("Unable to fill form"); 699 throw new CasperError("Unable to fill form");
...@@ -714,10 +718,10 @@ Casper.prototype.fill = function fill(selector, vals, submit) { ...@@ -714,10 +718,10 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
714 // Form submission? 718 // Form submission?
715 if (submit) { 719 if (submit) {
716 this.evaluate(function _evaluate(selector) { 720 this.evaluate(function _evaluate(selector) {
717 var form = window.__utils__.findOne(selector); 721 var form = __utils__.findOne(selector);
718 var method = (form.getAttribute('method') || "GET").toUpperCase(); 722 var method = (form.getAttribute('method') || "GET").toUpperCase();
719 var action = form.getAttribute('action') || "unknown"; 723 var action = form.getAttribute('action') || "unknown";
720 window.__utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info'); 724 __utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info');
721 if (typeof form.submit === "function") { 725 if (typeof form.submit === "function") {
722 form.submit(); 726 form.submit();
723 } else { 727 } else {
...@@ -826,7 +830,7 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) { ...@@ -826,7 +830,7 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) {
826 throw new CasperError("No element matching selector found: " + selector); 830 throw new CasperError("No element matching selector found: " + selector);
827 } 831 }
828 var clipRect = this.evaluate(function _evaluate(selector) { 832 var clipRect = this.evaluate(function _evaluate(selector) {
829 return window.__utils__.getElementBounds(selector); 833 return __utils__.getElementBounds(selector);
830 }, selector); 834 }, selector);
831 if (!utils.isClipRect(clipRect)) { 835 if (!utils.isClipRect(clipRect)) {
832 throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector); 836 throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector);
...@@ -835,6 +839,23 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) { ...@@ -835,6 +839,23 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) {
835 }; 839 };
836 840
837 /** 841 /**
842 * Retrieves information about the node matching the provided selector.
843 *
844 * @param String|Objects selector CSS3/XPath selector
845 * @return Object
846 */
847 Casper.prototype.getElementInfo = function getElementInfo(selector) {
848 "use strict";
849 this.checkStarted();
850 if (!this.exists(selector)) {
851 throw new CasperError(f("Cannot get informations from %s: element not found.", selector));
852 }
853 return this.evaluate(function(selector) {
854 return __utils__.getElementInfo(selector);
855 }, selector);
856 };
857
858 /**
838 * Retrieves boundaries for all the DOM elements matching the provided DOM CSS3/XPath selector. 859 * Retrieves boundaries for all the DOM elements matching the provided DOM CSS3/XPath selector.
839 * 860 *
840 * @param String selector A DOM CSS3/XPath selector 861 * @param String selector A DOM CSS3/XPath selector
...@@ -847,7 +868,7 @@ Casper.prototype.getElementsBounds = function getElementBounds(selector) { ...@@ -847,7 +868,7 @@ Casper.prototype.getElementsBounds = function getElementBounds(selector) {
847 throw new CasperError("No element matching selector found: " + selector); 868 throw new CasperError("No element matching selector found: " + selector);
848 } 869 }
849 return this.evaluate(function _evaluate(selector) { 870 return this.evaluate(function _evaluate(selector) {
850 return window.__utils__.getElementsBounds(selector); 871 return __utils__.getElementsBounds(selector);
851 }, selector); 872 }, selector);
852 }; 873 };
853 874
...@@ -883,7 +904,7 @@ Casper.prototype.getGlobal = function getGlobal(name) { ...@@ -883,7 +904,7 @@ Casper.prototype.getGlobal = function getGlobal(name) {
883 result.value = JSON.stringify(window[name]); 904 result.value = JSON.stringify(window[name]);
884 } catch (e) { 905 } catch (e) {
885 var message = f("Unable to JSON encode window.%s: %s", name, e); 906 var message = f("Unable to JSON encode window.%s: %s", name, e);
886 window.__utils__.log(message, "error"); 907 __utils__.log(message, "error");
887 result.error = message; 908 result.error = message;
888 } 909 }
889 return result; 910 return result;
...@@ -1015,7 +1036,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() { ...@@ -1015,7 +1036,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
1015 "use strict"; 1036 "use strict";
1016 this.checkStarted(); 1037 this.checkStarted();
1017 var clientUtilsInjected = this.page.evaluate(function() { 1038 var clientUtilsInjected = this.page.evaluate(function() {
1018 return typeof window.__utils__ === "object"; 1039 return typeof __utils__ === "object";
1019 }); 1040 });
1020 if (true === clientUtilsInjected) { 1041 if (true === clientUtilsInjected) {
1021 return; 1042 return;
...@@ -1120,7 +1141,7 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) { ...@@ -1120,7 +1141,7 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
1120 // PhantomJS doesn't provide native events for mouseover & mouseout 1141 // PhantomJS doesn't provide native events for mouseover & mouseout
1121 if (type === "mouseover" || type === "mouseout") { 1142 if (type === "mouseover" || type === "mouseout") {
1122 return this.evaluate(function(type, selector) { 1143 return this.evaluate(function(type, selector) {
1123 return window.__utils__.mouseEvent(type, selector); 1144 return __utils__.mouseEvent(type, selector);
1124 }, type, selector); 1145 }, type, selector);
1125 } 1146 }
1126 this.mouse.processEvent(type, selector); 1147 this.mouse.processEvent(type, selector);
...@@ -1307,6 +1328,45 @@ Casper.prototype.runStep = function runStep(step) { ...@@ -1307,6 +1328,45 @@ Casper.prototype.runStep = function runStep(step) {
1307 }; 1328 };
1308 1329
1309 /** 1330 /**
1331 * Sends keys to given element.
1332 *
1333 * @param String selector A DOM CSS3 compatible selector
1334 * @param String keys A string representing the sequence of char codes to send
1335 * @param Object options Options
1336 * @return Casper
1337 */
1338 Casper.prototype.sendKeys = function(selector, keys, options) {
1339 "use strict";
1340 if (phantom.version.major === 1 && phantom.version.minor < 7) {
1341 throw new CasperError('sendKeys() requires PhantomJS >= 1.7');
1342 }
1343 this.checkStarted();
1344 options = utils.mergeObjects({
1345 eventType: 'keypress'
1346 }, options || {});
1347 var elemInfos = this.getElementInfo(selector),
1348 tag = elemInfos.nodeName.toLowerCase(),
1349 type = utils.getPropertyPath(elemInfos, 'attributes.type'),
1350 supported = ["color", "date", "datetime", "datetime-local", "email",
1351 "hidden", "month", "number", "password", "range", "search",
1352 "tel", "text", "time", "url", "week"];
1353 var isTextInput = false;
1354 if (tag === 'textarea' || (tag === 'input' && supported.indexOf(type) !== -1)) {
1355 // clicking on the input element brings it focus
1356 isTextInput = true;
1357 this.click(selector);
1358 }
1359 this.page.sendEvent(options.eventType, keys);
1360 if (isTextInput) {
1361 // remove the focus
1362 this.evaluate(function(selector) {
1363 __utils__.findOne(selector).blur();
1364 }, selector);
1365 }
1366 return this;
1367 };
1368
1369 /**
1310 * Sets current WebPage instance the credentials for HTTP authentication. 1370 * Sets current WebPage instance the credentials for HTTP authentication.
1311 * 1371 *
1312 * @param String username 1372 * @param String username
...@@ -1558,7 +1618,7 @@ Casper.prototype.visible = function visible(selector) { ...@@ -1558,7 +1618,7 @@ Casper.prototype.visible = function visible(selector) {
1558 "use strict"; 1618 "use strict";
1559 this.checkStarted(); 1619 this.checkStarted();
1560 return this.evaluate(function _evaluate(selector) { 1620 return this.evaluate(function _evaluate(selector) {
1561 return window.__utils__.visible(selector); 1621 return __utils__.visible(selector);
1562 }, selector); 1622 }, selector);
1563 }; 1623 };
1564 1624
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
41 * Casper client-side helpers. 41 * Casper client-side helpers.
42 */ 42 */
43 exports.ClientUtils = function ClientUtils(options) { 43 exports.ClientUtils = function ClientUtils(options) {
44 /*jshint maxstatements:30*/ 44 /*jshint maxstatements:40*/
45 // private members 45 // private members
46 var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 46 var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
47 var BASE64_DECODE_CHARS = new Array( 47 var BASE64_DECODE_CHARS = new Array(
...@@ -393,6 +393,33 @@ ...@@ -393,6 +393,33 @@
393 }; 393 };
394 394
395 /** 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 /**
396 * Retrieves a single DOM element matching a given XPath expression. 423 * Retrieves a single DOM element matching a given XPath expression.
397 * 424 *
398 * @param String expression The XPath expression 425 * @param String expression The XPath expression
......
1 /*jshint strict:false*/
2 /*global CasperError casper console phantom require*/
3 if (phantom.version.major === 1 && phantom.version.minor < 7) {
4 casper.test.pass('Skipping tests for PhantomJS < 1.7');
5 casper.test.done(1);
6 }
7
8 casper.start('tests/site/form.html', function() {
9 this.sendKeys('input[name="email"]', 'duke@nuk.em');
10 this.sendKeys('textarea', "Damn, I’m looking good.");
11 var values = this.getFormValues('form');
12 this.test.assertEquals(values['email'], 'duke@nuk.em',
13 'Casper.sendKeys() sends keys to given input');
14 this.test.assertEquals(values['content'], "Damn, I’m looking good.",
15 'Casper.sendKeys() sends keys to given textarea');
16 });
17
18 casper.run(function() {
19 this.test.done(2);
20 });
...@@ -97,6 +97,7 @@ function fakeDocument(html) { ...@@ -97,6 +97,7 @@ function fakeDocument(html) {
97 // getElementsBounds 97 // getElementsBounds
98 casper.start(); 98 casper.start();
99 casper.then(function() { 99 casper.then(function() {
100 this.test.comment('Casper.getElementsBounds()');
100 var html = '<div id="boxes">'; 101 var html = '<div id="boxes">';
101 html += ' <div style="position:fixed;top:10px;left:11px;width:50px;height:60px"></div>'; 102 html += ' <div style="position:fixed;top:10px;left:11px;width:50px;height:60px"></div>';
102 html += ' <div style="position:fixed;top:20px;left:21px;width:70px;height:80px"></div>'; 103 html += ' <div style="position:fixed;top:20px;left:21px;width:70px;height:80px"></div>';
...@@ -110,6 +111,28 @@ function fakeDocument(html) { ...@@ -110,6 +111,28 @@ function fakeDocument(html) {
110 }); 111 });
111 })(casper); 112 })(casper);
112 113
114 (function(casper) {
115 // element information
116 casper.test.comment('ClientUtils.getElementInfo()');
117 casper.page.content = '<a href="plop" class="plip plup"><i>paf</i></a>';
118 var info = casper.getElementInfo('a.plip');
119 casper.test.assertEquals(info.nodeName, 'a', 'ClientUtils.getElementInfo() retrieves element name');
120 casper.test.assertEquals(info.attributes, {
121 'href': 'plop',
122 'class': 'plip plup'
123 }, 'ClientUtils.getElementInfo() retrieves element attributes');
124 casper.test.assertEquals(info.html, '<i>paf</i>', 'ClientUtils.getElementInfo() retrieves element html content');
125 casper.test.assertEquals(info.text, 'paf', 'ClientUtils.getElementInfo() retrieves element text');
126 casper.test.assert(info.x > 0, 'ClientUtils.getElementInfo() retrieves element x pos');
127 casper.test.assert(info.y > 0, 'ClientUtils.getElementInfo() retrieves element y pos');
128 casper.test.assert(info.width > 0, 'ClientUtils.getElementInfo() retrieves element width');
129 casper.test.assert(info.height > 0, 'ClientUtils.getElementInfo() retrieves element height');
130 casper.test.assert(info.visible, 'ClientUtils.getElementInfo() retrieves element visibility');
131 casper.test.assertEquals(info.tag, '<a href="plop" class="plip plup"><i>paf</i></a>',
132 'ClientUtils.getElementInfo() retrieves element whole tag contents');
133
134 })(casper);
135
113 casper.run(function() { 136 casper.run(function() {
114 this.test.done(30); 137 this.test.done(40);
115 }); 138 });
......