PageRenderTime 79ms CodeModel.GetById 2ms app.highlight 26ms RepoModel.GetById 44ms app.codeStats 0ms

/testing/selenium-core/scripts/selenium-remoterunner.js

http://datanucleus-appengine.googlecode.com/
JavaScript | 693 lines | 473 code | 64 blank | 156 comment | 116 complexity | 78f89134c65aa67813eddc7da4300dd6 MD5 | raw file
  1/*
  2* Copyright 2005 ThoughtWorks, Inc
  3*
  4*  Licensed under the Apache License, Version 2.0 (the "License");
  5*  you may not use this file except in compliance with the License.
  6*  You may obtain a copy of the License at
  7*
  8*      http://www.apache.org/licenses/LICENSE-2.0
  9*
 10*  Unless required by applicable law or agreed to in writing, software
 11*  distributed under the License is distributed on an "AS IS" BASIS,
 12*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13*  See the License for the specific language governing permissions and
 14*  limitations under the License.
 15*
 16*/
 17
 18passColor = "#cfffcf";
 19failColor = "#ffcfcf";
 20errorColor = "#ffffff";
 21workingColor = "#DEE7EC";
 22doneColor = "#FFFFCC";
 23
 24var injectedSessionId;
 25
 26var postResult = "START";
 27var debugMode = false;
 28var relayToRC = null;
 29var proxyInjectionMode = false;
 30var uniqueId = 'sel_' + Math.round(100000 * Math.random());
 31var seleniumSequenceNumber = 0;
 32var cmd8 = "";
 33var cmd7 = "";
 34var cmd6 = "";
 35var cmd5 = "";
 36var cmd4 = "";
 37var cmd3 = "";
 38var cmd2 = "";
 39var cmd1 = "";
 40var lastCmd = "";
 41var lastCmdTime = new Date();
 42
 43var RemoteRunnerOptions = classCreate();
 44objectExtend(RemoteRunnerOptions.prototype, URLConfiguration.prototype);
 45objectExtend(RemoteRunnerOptions.prototype, {
 46    initialize: function() {
 47        this._acquireQueryString();
 48    },
 49    isDebugMode: function() {
 50        return this._isQueryParameterTrue("debugMode");
 51    },
 52
 53    getContinue: function() {
 54        return this._getQueryParameter("continue");
 55    },
 56
 57    getDriverUrl: function() {
 58        return this._getQueryParameter("driverUrl");
 59    },
 60
 61    // requires per-session extension Javascript as soon as this Selenium
 62    // instance becomes aware of the session identifier
 63    getSessionId: function() {
 64        var sessionId = this._getQueryParameter("sessionId");
 65        requireExtensionJs(sessionId);
 66        return sessionId;
 67    },
 68
 69    _acquireQueryString: function () {
 70        if (this.queryString) return;
 71        if (browserVersion.isHTA) {
 72            var args = this._extractArgs();
 73            if (args.length < 2) return null;
 74            this.queryString = args[1];
 75        } else if (proxyInjectionMode) {
 76            this.queryString = window.location.search.substr(1);
 77        } else {
 78            this.queryString = top.location.search.substr(1);
 79        }
 80    }
 81
 82});
 83var runOptions;
 84
 85function runSeleniumTest() {
 86    runOptions = new RemoteRunnerOptions();
 87    var testAppWindow;
 88
 89    if (runOptions.isMultiWindowMode()) {
 90        testAppWindow = openSeparateApplicationWindow('Blank.html', true);
 91    } else if (sel$('selenium_myiframe') != null) {
 92        var myiframe = sel$('selenium_myiframe');
 93        if (myiframe) {
 94            testAppWindow = myiframe.contentWindow;
 95        }
 96    }
 97    else {
 98        proxyInjectionMode = true;
 99        testAppWindow = window;
100    }
101    selenium = Selenium.createForWindow(testAppWindow, proxyInjectionMode);
102    if (runOptions.getBaseUrl()) {
103        selenium.browserbot.baseUrl = runOptions.getBaseUrl();
104    }
105    if (!debugMode) {
106        debugMode = runOptions.isDebugMode();
107    }
108    if (proxyInjectionMode) {
109        LOG.logHook = logToRc;
110        selenium.browserbot._modifyWindow(testAppWindow);
111    }
112    else if (debugMode) {
113        LOG.logHook = logToRc;
114    }
115    window.selenium = selenium;
116
117    commandFactory = new CommandHandlerFactory();
118    commandFactory.registerAll(selenium);
119
120    currentTest = new RemoteRunner(commandFactory);
121
122    var doContinue = runOptions.getContinue();
123    if (doContinue != null) postResult = "OK";
124
125    currentTest.start();
126}
127
128function buildDriverUrl() {
129    var driverUrl = runOptions.getDriverUrl();
130    if (driverUrl != null) {
131        return driverUrl;
132    }
133    var s = window.location.href
134    var slashPairOffset = s.indexOf("//") + "//".length
135    var pathSlashOffset = s.substring(slashPairOffset).indexOf("/")
136    return s.substring(0, slashPairOffset + pathSlashOffset) + "/selenium-server/driver/";
137    //return "http://localhost" + uniqueId + "/selenium-server/driver/";
138}
139
140function logToRc(logLevel, message) {
141    if (debugMode) {
142        if (logLevel == null) {
143            logLevel = "debug";
144        }
145        sendToRCAndForget("logLevel=" + logLevel + ":" + message.replace(/[\n\r\015]/g, " ") + "\n", "logging=true");
146    }
147}
148
149function serializeString(name, s) {
150    return name + "=unescape(\"" + escape(s) + "\");";
151}
152
153function serializeObject(name, x)
154{
155    var s = '';
156
157    if (isArray(x))
158    {
159        s = name + "=new Array(); ";
160        var len = x["length"];
161        for (var j = 0; j < len; j++)
162        {
163            s += serializeString(name + "[" + j + "]", x[j]);
164        }
165    }
166    else if (typeof x == "string")
167    {
168        s = serializeString(name, x);
169    }
170    else
171    {
172        throw "unrecognized object not encoded: " + name + "(" + x + ")";
173    }
174    return s;
175}
176
177function relayBotToRC(s) {
178}
179
180// seems like no one uses this, but in fact it is called using eval from server-side PI mode code; however,
181// because multiple names can map to the same popup, assigning a single name confuses matters sometimes;
182// thus, I'm disabling this for now.  -Nelson 10/21/06
183function setSeleniumWindowName(seleniumWindowName) {
184//selenium.browserbot.getCurrentWindow()['seleniumWindowName'] = seleniumWindowName;
185}
186
187RemoteRunner = classCreate();
188objectExtend(RemoteRunner.prototype, new TestLoop());
189objectExtend(RemoteRunner.prototype, {
190    initialize : function(commandFactory) {
191        this.commandFactory = commandFactory;
192        this.requiresCallBack = true;
193        this.commandNode = null;
194        this.xmlHttpForCommandsAndResults = null;
195    },
196
197    nextCommand : function() {
198        var urlParms = "";
199        if (postResult == "START") {
200            urlParms += "seleniumStart=true";
201        }
202        this.xmlHttpForCommandsAndResults = XmlHttp.create();
203        sendToRC(postResult, urlParms, fnBind(this._HandleHttpResponse, this), this.xmlHttpForCommandsAndResults);
204    },
205
206    commandStarted : function(command) {
207        this.commandNode = document.createElement("div");
208        var cmdText = command.command + '(';
209        if (command.target != null && command.target != "") {
210            cmdText += command.target;
211            if (command.value != null && command.value != "") {
212                cmdText += ', ' + command.value;
213            }
214        }
215        if (cmdText.length > 70) {
216            cmdText = cmdText.substring(0, 70) + "...\n";
217        } else {
218            cmdText += ")\n";
219        }
220
221        if (cmdText == lastCmd) {
222	        var rightNow = new Date();
223	        var msSinceStart = rightNow.getTime() - lastCmdTime.getTime();
224	        var sinceStart = msSinceStart + "ms";
225	        if (msSinceStart > 1000) {
226		        sinceStart = Math.round(msSinceStart / 1000) + "s";
227		    }
228            cmd1 = "Same command (" + sinceStart + "): " + lastCmd;
229        } else {
230	        lastCmdTime = new Date();
231            cmd8 = cmd7;
232            cmd7 = cmd6;
233            cmd6 = cmd5;
234            cmd5 = cmd4;
235            cmd4 = cmd3;
236            cmd3 = cmd2;
237            cmd2 = cmd1;
238            cmd1 = cmdText;
239        }
240        lastCmd = cmdText;
241        var commandList = document.commands.commandList;
242        commandList.value = cmd8 + cmd7 + cmd6 + cmd5 + cmd4 + cmd3 + cmd2 + cmd1;
243        commandList.scrollTop = commandList.scrollHeight;
244
245    },
246
247    commandComplete : function(result) {
248
249        if (result.failed) {
250            if (postResult == "CONTINUATION") {
251                currentTest.aborted = true;
252            }
253            postResult = result.failureMessage;
254            this.commandNode.title = result.failureMessage;
255            this.commandNode.style.backgroundColor = failColor;
256        } else if (result.passed) {
257            postResult = "OK";
258            this.commandNode.style.backgroundColor = passColor;
259        } else {
260            if (result.result == null) {
261                postResult = "OK";
262            } else {
263                var actualResult = result.result;
264                actualResult = selArrayToString(actualResult);
265                postResult = "OK," + actualResult;
266            }
267            this.commandNode.style.backgroundColor = doneColor;
268        }
269    },
270
271    commandError : function(message) {
272        postResult = "ERROR: " + message;
273        this.commandNode.style.backgroundColor = errorColor;
274        this.commandNode.titcle = message;
275    },
276
277    testComplete : function() {
278        window.status = "Selenium Tests Complete, for this Test"
279        // Continue checking for new results
280        this.continueTest();
281        postResult = "START";
282    },
283
284    _HandleHttpResponse : function() {
285        // When request is completed
286        if (this.xmlHttpForCommandsAndResults.readyState == 4) {
287            // OK
288            if (this.xmlHttpForCommandsAndResults.status == 200) {
289            	if (this.xmlHttpForCommandsAndResults.responseText=="") {
290                    LOG.error("saw blank string xmlHttpForCommandsAndResults.responseText");
291                    return;
292                }
293                var command = this._extractCommand(this.xmlHttpForCommandsAndResults);
294                if (command.command == 'retryLast') {
295                    setTimeout(fnBind(function() {
296                        sendToRC("RETRY", "retry=true", fnBind(this._HandleHttpResponse, this), this.xmlHttpForCommandsAndResults, true);
297                    }, this), 1000);
298                } else {
299                    this.currentCommand = command;
300                    this.continueTestAtCurrentCommand();
301                }
302            }
303            // Not OK 
304            else {
305                var s = 'xmlHttp returned: ' + this.xmlHttpForCommandsAndResults.status + ": " + this.xmlHttpForCommandsAndResults.statusText;
306                LOG.error(s);
307                this.currentCommand = null;
308                setTimeout(fnBind(this.continueTestAtCurrentCommand, this), 2000);
309            }
310
311        }
312    },
313
314    _extractCommand : function(xmlHttp) {
315        var command, text, json;
316        text = command = xmlHttp.responseText;
317        if (/^json=/.test(text)) {
318            eval(text);
319            if (json.rest) {
320                eval(json.rest);
321            }
322            return json;
323        }
324        try {
325            var re = new RegExp("^(.*?)\n((.|[\r\n])*)");
326            if (re.exec(xmlHttp.responseText)) {
327                command = RegExp.$1;
328                var rest = RegExp.$2;
329                rest = rest.trim();
330                if (rest) {
331                    eval(rest);
332                }
333            }
334            else {
335                command = xmlHttp.responseText;
336            }
337        } catch (e) {
338            alert('could not get responseText: ' + e.message);
339        }
340        if (command.substr(0, '|testComplete'.length) == '|testComplete') {
341            return null;
342        }
343
344        return this._createCommandFromRequest(command);
345    },
346
347
348    _delay : function(millis) {
349        var startMillis = new Date();
350        while (true) {
351            milli = new Date();
352            if (milli - startMillis > millis) {
353                break;
354            }
355        }
356    },
357
358// Parses a URI query string into a SeleniumCommand object
359    _createCommandFromRequest : function(commandRequest) {
360        //decodeURIComponent doesn't strip plus signs
361        var processed = commandRequest.replace(/\+/g, "%20");
362        // strip trailing spaces
363        var processed = processed.replace(/\s+$/, "");
364        var vars = processed.split("&");
365        var cmdArgs = new Object();
366        for (var i = 0; i < vars.length; i++) {
367            var pair = vars[i].split("=");
368            cmdArgs[pair[0]] = pair[1];
369        }
370        var cmd = cmdArgs['cmd'];
371        var arg1 = cmdArgs['1'];
372        if (null == arg1) arg1 = "";
373        arg1 = decodeURIComponent(arg1);
374        var arg2 = cmdArgs['2'];
375        if (null == arg2) arg2 = "";
376        arg2 = decodeURIComponent(arg2);
377        if (cmd == null) {
378            throw new Error("Bad command request: " + commandRequest);
379        }
380        return new SeleniumCommand(cmd, arg1, arg2);
381    }
382
383})
384
385
386function sendToRC(dataToBePosted, urlParms, callback, xmlHttpObject, async) {
387    if (async == null) {
388        async = true;
389    }
390    if (xmlHttpObject == null) {
391        xmlHttpObject = XmlHttp.create();
392    }
393    var url = buildDriverUrl() + "?"
394    if (urlParms) {
395        url += urlParms;
396    }
397    url = addUrlParams(url);
398    url += "&sequenceNumber=" + seleniumSequenceNumber++;
399    
400    var postedData = "postedData=" + encodeURIComponent(dataToBePosted);
401
402    //xmlHttpObject.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
403    xmlHttpObject.open("POST", url, async);
404    if (callback) xmlHttpObject.onreadystatechange = callback;
405    xmlHttpObject.send(postedData);
406    return null;
407}
408
409function addUrlParams(url) {
410    return url + "&localFrameAddress=" + (proxyInjectionMode ? makeAddressToAUTFrame() : "top")
411    + getSeleniumWindowNameURLparameters()
412    + "&uniqueId=" + uniqueId
413    + buildDriverParams() + preventBrowserCaching()
414}
415
416function sendToRCAndForget(dataToBePosted, urlParams) {
417    var url;
418    if (!(browserVersion.isChrome || browserVersion.isHTA)) { 
419        // DGF we're behind a proxy, so we can send our logging message to literally any host, to avoid 2-connection limit
420        var protocol = "http:";
421        if (window.location.protocol == "https:") {
422            // DGF if we're in HTTPS, use another HTTPS url to avoid security warning
423            protocol = "https:";
424        }
425        // we don't choose a super large random value, but rather 1 - 16, because this matches with the pre-computed
426        // tunnels waiting on the Selenium Server side. This gives us higher throughput than the two-connection-per-host
427        // limitation, but doesn't require we generate an extremely large ammount of fake SSL certs either.
428        url = protocol + "//" + Math.floor(Math.random()* 16 + 1) + ".selenium.doesnotexist/selenium-server/driver/?" + urlParams;
429    } else {
430        url = buildDriverUrl() + "?" + urlParams;
431    }
432    url = addUrlParams(url);
433    
434    var method = "GET";
435    if (method == "POST") {
436        // DGF submit a request using an iframe; we can't see the response, but we don't need to
437        // TODO not using this mechanism because it screws up back-button
438        var loggingForm = document.createElement("form");
439        loggingForm.method = "POST";
440        loggingForm.action = url;
441        loggingForm.target = "seleniumLoggingFrame";
442        var postedDataInput = document.createElement("input");
443        postedDataInput.type = "hidden";
444        postedDataInput.name = "postedData";
445        postedDataInput.value = dataToBePosted;
446        loggingForm.appendChild(postedDataInput);
447        document.body.appendChild(loggingForm);
448        loggingForm.submit();
449        document.body.removeChild(loggingForm);
450    } else {
451        var postedData = "&postedData=" + encodeURIComponent(dataToBePosted);
452        var scriptTag = document.createElement("script");
453        scriptTag.src = url + postedData;
454        document.body.appendChild(scriptTag);
455        document.body.removeChild(scriptTag);
456    }
457}
458
459function buildDriverParams() {
460    var params = "";
461
462    var sessionId = runOptions.getSessionId();
463    if (sessionId == undefined) {
464        sessionId = injectedSessionId;
465    }
466    if (sessionId != undefined) {
467        params = params + "&sessionId=" + sessionId;
468    }
469    return params;
470}
471
472function preventBrowserCaching() {
473    var t = (new Date()).getTime();
474    return "&counterToMakeURsUniqueAndSoStopPageCachingInTheBrowser=" + t;
475}
476
477//
478// Return URL parameters pertaining to the name(s?) of the current window
479//
480// In selenium, the main (i.e., first) window's name is a blank string.
481//
482// Additional pop-ups are associated with either 1.) the name given by the 2nd parameter to window.open, or 2.) the name of a
483// property on the opening window which points at the window.
484//
485// An example of #2: if window X contains JavaScript as follows:
486//
487// 	var windowABC = window.open(...)
488//
489// Note that the example JavaScript above is equivalent to
490//
491// 	window["windowABC"] = window.open(...)
492//
493function getSeleniumWindowNameURLparameters() {
494    var w = (proxyInjectionMode ? selenium.browserbot.getCurrentWindow() : window).top;
495    var s = "&seleniumWindowName=";
496    if (w.opener == null) {
497        return s;
498    }
499    if (w["seleniumWindowName"] == null) {
500        if (w.name) {
501            w["seleniumWindowName"] = w.name;
502        } else {
503    	    w["seleniumWindowName"] = 'generatedSeleniumWindowName_' + Math.round(100000 * Math.random());
504    	}
505    }
506    s += w["seleniumWindowName"];
507    var windowOpener = w.opener;
508    for (key in windowOpener) {
509        var val = null;
510        try {
511    	    val = windowOpener[key];
512        }
513        catch(e) {
514        }
515        if (val==w) {
516	    s += "&jsWindowNameVar=" + key;			// found a js variable in the opener referring to this window
517        }
518    }
519    return s;
520}
521
522// construct a JavaScript expression which leads to my frame (i.e., the frame containing the window
523// in which this code is operating)
524function makeAddressToAUTFrame(w, frameNavigationalJSexpression)
525{
526    if (w == null)
527    {
528        w = top;
529        frameNavigationalJSexpression = "top";
530    }
531
532    if (w == selenium.browserbot.getCurrentWindow())
533    {
534        return frameNavigationalJSexpression;
535    }
536    for (var j = 0; j < w.frames.length; j++)
537    {
538        var t = makeAddressToAUTFrame(w.frames[j], frameNavigationalJSexpression + ".frames[" + j + "]");
539        if (t != null)
540        {
541            return t;
542        }
543    }
544    return null;
545}
546
547Selenium.prototype.doSetContext = function(context) {
548    /**
549   * Writes a message to the status bar and adds a note to the browser-side
550   * log.
551   *
552   * @param context
553   *            the message to be sent to the browser
554   */
555    //set the current test title
556    var ctx = document.getElementById("context");
557    if (ctx != null) {
558        ctx.innerHTML = context;
559    }
560};
561
562/**
563 * Adds a script tag referencing a specially-named user extensions "file". The
564 * resource handler for this special file (which won't actually exist) will use
565 * the session ID embedded in its name to retrieve per-session specified user
566 * extension javascript.
567 *
568 * @param sessionId
569 */
570function requireExtensionJs(sessionId) {
571    var src = 'scripts/user-extensions.js[' + sessionId + ']';
572    if (document.getElementById(src) == null) {
573        var scriptTag = document.createElement('script');
574        scriptTag.language = 'JavaScript';
575        scriptTag.type = 'text/javascript';
576        scriptTag.src = src;
577        scriptTag.id = src;
578        var headTag = document.getElementsByTagName('head')[0];
579        headTag.appendChild(scriptTag);
580    }
581}
582
583Selenium.prototype.doAttachFile = function(fieldLocator,fileLocator) {
584   /**
585   * Sets a file input (upload) field to the file listed in fileLocator
586   *
587   *  @param fieldLocator an <a href="#locators">element locator</a>
588   *  @param fileLocator a URL pointing to the specified file. Before the file
589   *  can be set in the input field (fieldLocator), Selenium RC may need to transfer the file  
590   *  to the local machine before attaching the file in a web page form. This is common in selenium
591   *  grid configurations where the RC server driving the browser is not the same
592   *  machine that started the test.
593   *
594   *  Supported Browsers: Firefox ("*chrome") only.
595   *   
596   */
597   // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us! 
598};
599
600Selenium.prototype.doCaptureScreenshot = function(filename) {
601    /**
602    * Captures a PNG screenshot to the specified file.
603    *
604    * @param filename the absolute path to the file to be written, e.g. "c:\blah\screenshot.png"
605    */
606    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
607};
608
609Selenium.prototype.doCaptureScreenshotToString = function() {
610    /**
611    * Capture a PNG screenshot.  It then returns the file as a base 64 encoded string. 
612    * 
613    * @return string The base 64 encoded string of the screen shot (PNG file)
614    */
615    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
616};
617
618Selenium.prototype.doCaptureEntirePageScreenshotToString = function(kwargs) {
619    /**
620    * Downloads a screenshot of the browser current window canvas to a 
621    * based 64 encoded PNG file. The <em>entire</em> windows canvas is captured,
622    * including parts rendered outside of the current view port.
623    *
624	* Currently this only works in Mozilla and when running in chrome mode. 
625    * 
626    * @param kwargs  A kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD". This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed  (possibly obscuring black text).
627    *
628    * @return string The base 64 encoded string of the page screenshot (PNG file)
629    */
630    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
631};
632
633Selenium.prototype.doShutDownSeleniumServer = function(keycode) {
634    /**
635    * Kills the running Selenium Server and all browser sessions.  After you run this command, you will no longer be able to send
636    * commands to the server; you can't remotely start the server once it has been stopped.  Normally
637    * you should prefer to run the "stop" command, which terminates the current browser session, rather than 
638    * shutting down the entire server.
639    *
640    */
641    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
642};
643
644Selenium.prototype.doRetrieveLastRemoteControlLogs = function() {
645    /**
646    * Retrieve the last messages logged on a specific remote control. Useful for error reports, especially
647    * when running multiple remote controls in a distributed environment. The maximum number of log messages
648    * that can be retrieve is configured on remote control startup.
649    *
650    * @return string The last N log messages as a multi-line string.
651    */
652    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
653};
654
655Selenium.prototype.doKeyDownNative = function(keycode) {
656    /**
657    * Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke.
658    * This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
659    * a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
660    * metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
661    * element, focus on the element first before running this command.
662    *
663    * @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
664    */
665    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
666};
667
668Selenium.prototype.doKeyUpNative = function(keycode) {
669    /**
670    * Simulates a user releasing a key by sending a native operating system keystroke.
671    * This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
672    * a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
673    * metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
674    * element, focus on the element first before running this command.
675    *
676    * @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
677    */
678    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
679};
680
681Selenium.prototype.doKeyPressNative = function(keycode) {
682    /**
683    * Simulates a user pressing and releasing a key by sending a native operating system keystroke.
684    * This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
685    * a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
686    * metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
687    * element, focus on the element first before running this command.
688    *
689    * @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
690    */
691    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
692};
693