I wrote this post a few months back and then went on a holiday without posting it. By the time I was back, I had completely forgotten about it. Perhaps, stumbling upon it today is a sign that I should post it now, even though the topic may have been beaten to death already.
The problem with typeKeys in Selenium is a known problem. While searching on the internet, I came across the issue 483/SIDE-309. Even though, I did not face the exact same problem, it seemed that it affected more functions than was reported. I decided to dig in further. Jeremy had rightly said, it is probably a problem in Selenium Core.
Having worked with the keyboard since the early days of MSDOS, I have a good deal of knowledge on how it actually works on the Windows platform. But since I had never experimented with the keyboard events inside the browser, I decided to write a small web page to explore how the keyboard events actually work. I have listed the web page below so you can try it out as well. As you type into the input field, the events generated are captured and the details are shown. Comparing it with the Selenium playback immediately highlighted a few issues. Firstly, the event sequence was incorrect for the typeKeys command. The values provided to the event were also sometimes incorrect.
I still remember my very old programming days, the days of TSRs, INT 9 and in and out of 60h. Its amazing how you can recall such details after ten? twenty years? The important part was that the keyboard did not exactly generate ASCII codes, it would generate scan codes that would need to be translated into ASCII codes by the BIOS or the OS. Time for a bit of googling to see how they behave nowadays. There are three types of key events, keyUp, keyPress and keyDown. The event handler for these events is passed an event object whose fields provide information about the event. In Firefox, some of the useful fields of the event object are altKey, charCode, ctrlKey, isChar, keyCode, shiftKey. The keyCode and charCode are particularly interesting.
The Mozilla documentation provided this description:-
…event.keyCode returns the Unicode value of a non-character key in a keypress event or any key in any other type of keyboard event. For constants equivalent to these numeric codes, see KeyEvent…
A bit ambiguous for me. The KeyEvent table did describe the key code values and they looked suspiciously familiar to me.
For Firefox, the keyCode passed to the event is what is traditionally known as a virtual key code to windows programmers. This is a special code that is then converted to the ASCII/Unicode value of the character. The important thing to note is that not all virtual keys have a ASCII/Unicode character associated with it. You can also have more than one keys producing the same ASCII code, for example the numeric keys on the main section and the numeric keypad. Another complication is that depending on the keyboard layout, the mapping for some keys will change. More on the topic in the links below.
A quick look at the Microsoft documentation confirmed the same, but introduced some new complications. In IE, the keyCode can be different based on the event. Sometimes (in KeyPress) it can be the ASCII code and sometimes (KeyUp and KeyDown) the virtual key code.
To cut a long story short. The usual event sequence is KeyDown, KeyPress, KeyUp. KeyUp and KeyDown will send the virtual key codes, where as the KeyPress will send the ASCII/Unicode. Firefox and IE have different ways of sending the event information as well.
The Fix
The TypeKeys command is defined in selenium-api.js and is a convenience method that simply calls KeyDown, KeyUp and KeyPress for every character in the string.
Selenium.prototype.doTypeKeys = function(locator, value) { var keys = new String(value).split(""); for (var i = 0; i < keys.length; i++) { var c = keys[i]; this.doKeyDown(locator, c); this.doKeyPress(locator, c); //Samit: Fix wrong order of the events this.doKeyUp(locator, c); } };
Since the order of events was wrong, I simply corrected it, i.e. KeyPress should occur before KeyUp. It also means that both keyDown as well as keyUp commands of Selenium suffer from the problem.
The keyDown, keyPress and keyUp commands are implemented by the doKeyDown, doKeyPress and doKeyUp functions, which, delegate the heavy work to another function triggerKeyEvent, defined in htmlutils.js. I targetted the triggerKeyEvent function for the fix and hammered out some code and some test cases to check it. It seemed fixed! I discovered one weird situation though. But that will be a story for another day. The fix consists of converting the ASCII code to the virtual key code on a best effort basis whenever required and sending the appropriate code for the event in the expected field of the event object. This would automatically fix the problems with typeKeys, keyUp as well as keyDown commands of Selenium Core.
function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown, virtualKeyCode) { var keycode = 0; if (keySequence) { keycode = getKeyCodeFromKeySequence(keySequence); } var vkeycode = virtualKeyCode; if (!virtualKeyCode) { vkeycode = getVKeyCodeFromKeyCode(keycode); } canBubble = (typeof(canBubble) == undefined) ? true : canBubble; if (element.fireEvent && element.ownerDocument && element.ownerDocument.createEventObject) { // IE var keyEvent = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown); keyEvent.keyCode = (eventType == 'keypress') ? keycode : vkeycode; //Samit: Fix: IE sets // the keycode to be ascii/unicode for keypress events, // and the virtual key for keyup/keydown events element.fireEvent('on' + eventType, keyEvent); } else { var evt; if (window.KeyEvent) { evt = document.createEvent('KeyEvents'); //Samit: Fix: set the keycode to be ascii/unicode for keypress events, // and the virtual key for keyup/keydown events if (eventType == 'keypress') { evt.initKeyEvent(eventType, true, true, window, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown, 0, keycode); } else { evt.initKeyEvent(eventType, true, true, window, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown, vkeycode, 0); } } else { evt = document.createEvent('UIEvents'); evt.shiftKey = shiftKeyDown; evt.metaKey = metaKeyDown; evt.altKey = altKeyDown; evt.ctrlKey = controlKeyDown; evt.initUIEvent(eventType, true, true, window, 1); //Samit: Fix: set the keycode to be ascii/unicode for //keypress events, and the virtual key for keyup/keydown events if (eventType == 'keypress') { evt.charCode = keycode; evt.keyCode = 0; evt.which = keycode; } else { evt.charCode = 0; evt.keyCode = vkeycode; evt.which = vkeycode; } } element.dispatchEvent(evt); } }
I also wrote a function that converts the ASCII codes to the virtual code that is now used by the triggerKeyEvent function. Here it is.
/* Samit: Enh: Added function getVKeyCodeFromKeyCode to convert common ascii * chars to virtual key codes on a best effort basis * getVKeyCodeFromKeyCode: Return the virtual key code corresponding * to the the ascii code. These codes come from a capture of my keyboard * and may be different for different keyboard layouts/languages. * Note: keyCode is a number, not a string */ function getVKeyCodeFromKeyCode(keyCode) { var keyChar = String.fromCharCode(keyCode); if (/[0-9A-Z ;]/.exec(keyChar)) { return keyCode; } if (/[a-z]/.exec(keyChar)) { return keyChar.toUpperCase().charCodeAt(0); } var i = ")!@#$%^&*(x:".indexOf(keyChar); if (i != -1) { return i + 48; } i = "<,xx.>/?`~".indexOf(keyChar); if (i != -1) { return ((i - (i%2)) /2) + 188; } i = "[{\\|]}'\"".indexOf(keyChar); if (i != -1) { return ((i - (i%2)) /2) + 219; } if (keyChar == '+' || keyChar == '=') { return 107; } if (keyChar == '-' || keyChar == '_') { return 109; } if (keyCode == 8 || keyCode == 9 || keyCode == 13 || keyCode == 27) { return keyCode; } return 0; }
The fix is far from perfect, but it does improve things a bit for Firefox. Check out the “JavaScript Madness: Keyboard Events” in the links below.
Applying Changes to Selenium 1.x
Selenium 2 does not suffer from these problems. To fix the problem in Selenium 1.x, if using htmlsuite option of Selenium RC is your thing, means updating the two files htmlutils.js and selenium-api.js in the “\core\scripts” folder in the server jar file with the given code.
For Selenium IDE, a similar approach would work but the required folder is: chrome\content\selenium\scripts
Experimenting With Keyboard Events:
You will need the excellent jQuery library for this to work.
<html> <head> <title>Explore Keyboard Events</title> <script type="text/javascript" src="jquery-1.4.2.min.js"></script> <script type="text/javascript"> function acsiiChar(a) { if (a >= 32 && a < 127) { return '(' + String.fromCharCode(a) + ')'; } return ''; } function showEvent(e) { var s = ''; s = e.type; s += '(' + (e.ctrlKey ? 'C' : 'c') + (e.altKey ? 'A' : 'a') + (e.shiftKey ? 'S' : 's') + (e.metaKey ? 'M' : 'm') + (e.isChar ? 'I' : 'i') + ')'; if (e.type == 'keypress') { s += '[charCode:' + e.charCode + acsiiChar(e.charCode) + ']'; }else { s += '[charCode:0]'; } s += '[keyCode:' + e.keyCode + acsiiChar(e.keyCode) + ']'; s += '[which:' + e.which + acsiiChar(e.which) + ']'; $('#info').text($('#info').text() + ' ' + s); } </script> </head> <body> <h1>Explore Keyboard Events</h1> <form> Type keys in the following field to observe the events <input id="q" onKeyPress="showEvent(event);" onKeyDown="showEvent(event);" onKeyUp="showEvent(event);" /> <a href="#" id="clear" onClick="$('#q').val(''); $('#info').text(''); return false;"> Clear</a><br> </form> <div id="info" /> </body> </html>
Here are some interesting links I found on the topic:
https://developer.mozilla.org/en/DOM/event.initKeyEvent
https://developer.mozilla.org/en/DOM/event.keyCode
https://developer.mozilla.org/en/DOM/Event/UIEvent/KeyEvent
https://developer.mozilla.org/en/DOM/event.charCode
https://developer.mozilla.org/en/DOM/event.which
https://developer.mozilla.org/en/Gecko_Keypress_Event
http://msdn.microsoft.com/en-us/library/ms533927%28v=VS.85%29.aspx
http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx
http://www.quirksmode.org/js/keys.html
http://ejohn.org/blog/keypress-in-safari-31/
http://unixpapa.com/js/key.html
http://www.w3.org/TR/2001/WD-DOM-Level-3-Events-20010410/DOM3-Events.html#events-Events-KeyEvent
Case solved! For me 😀
Thankx that helped a lot !!