added Casper.sendKeys() - refs #302
to send native keyboard events to the element matching a given selector.
Showing
5 changed files
with
146 additions
and
15 deletions
... | @@ -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 | ... | ... |
tests/suites/casper/keys.js
0 → 100644
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 | }); | ... | ... |
-
Please register or sign in to post a comment