/testing/selenium-core/scripts/selenium-commandhandlers.js
JavaScript | 377 lines | 257 code | 40 blank | 80 comment | 38 complexity | 08074b10965a1ee2b2513041d7558e8d MD5 | raw file
1/* 2* Copyright 2004 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// A naming convention used in this file: 18// 19// 20// - a "seleniumApi" is an instance of the Selenium object, defined in selenium-api.js. 21// 22// - a "Method" is an unbound function whose target must be supplied when it's called, ie. 23// it should be invoked using Function.call() or Function.apply() 24// 25// - a "Block" is a function that has been bound to a target object, so can be called invoked directly 26// (or with a null target) 27// 28// - "CommandHandler" is effectively an abstract base for 29// various handlers including ActionHandler, AccessorHandler and AssertHandler. 30// Subclasses need to implement an execute(seleniumApi, command) function, 31// where seleniumApi is the Selenium object, and command a SeleniumCommand object. 32// 33// - Handlers will return a "result" object (ActionResult, AccessorResult, AssertResult). 34// ActionResults may contain a .terminationCondition function which is run by 35// -executionloop.js after the command is run; we'll run it over and over again 36// until it returns true or the .terminationCondition throws an exception. 37// AccessorResults will contain the results of running getter (e.g. getTitle returns 38// the title as a string). 39 40var CommandHandlerFactory = classCreate(); 41objectExtend(CommandHandlerFactory.prototype, { 42 43 initialize: function() { 44 this.handlers = {}; 45 }, 46 47 registerAction: function(name, actionBlock, wait, dontCheckAlertsAndConfirms) { 48 this.handlers[name] = new ActionHandler(actionBlock, wait, dontCheckAlertsAndConfirms); 49 }, 50 51 registerAccessor: function(name, accessBlock) { 52 this.handlers[name] = new AccessorHandler(accessBlock); 53 }, 54 55 registerAssert: function(name, assertBlock, haltOnFailure) { 56 this.handlers[name] = new AssertHandler(assertBlock, haltOnFailure); 57 }, 58 59 getCommandHandler: function(name) { 60 return this.handlers[name]; 61 }, 62 63 _registerAllAccessors: function(seleniumApi) { 64 // Methods of the form getFoo(target) result in commands: 65 // getFoo, assertFoo, verifyFoo, assertNotFoo, verifyNotFoo 66 // storeFoo, waitForFoo, and waitForNotFoo. 67 for (var functionName in seleniumApi) { 68 var match = /^(get|is)([A-Z].+)$/.exec(functionName); 69 if (match) { 70 var accessMethod = seleniumApi[functionName]; 71 var accessBlock = fnBind(accessMethod, seleniumApi); 72 var baseName = match[2]; 73 var isBoolean = (match[1] == "is"); 74 var requiresTarget = (accessMethod.length == 1); 75 76 this.registerAccessor(functionName, accessBlock); 77 this._registerStoreCommandForAccessor(baseName, accessBlock, requiresTarget); 78 79 var predicateBlock = this._predicateForAccessor(accessBlock, requiresTarget, isBoolean); 80 this._registerAssertionsForPredicate(baseName, predicateBlock); 81 this._registerWaitForCommandsForPredicate(seleniumApi, baseName, predicateBlock); 82 } 83 } 84 }, 85 86 _registerAllActions: function(seleniumApi) { 87 for (var functionName in seleniumApi) { 88 var match = /^do([A-Z].+)$/.exec(functionName); 89 if (match) { 90 var actionName = match[1].lcfirst(); 91 var actionMethod = seleniumApi[functionName]; 92 var dontCheckPopups = actionMethod.dontCheckAlertsAndConfirms; 93 var actionBlock = fnBind(actionMethod, seleniumApi); 94 this.registerAction(actionName, actionBlock, false, dontCheckPopups); 95 this.registerAction(actionName + "AndWait", actionBlock, true, dontCheckPopups); 96 } 97 } 98 }, 99 100 _registerAllAsserts: function(seleniumApi) { 101 for (var functionName in seleniumApi) { 102 var match = /^assert([A-Z].+)$/.exec(functionName); 103 if (match) { 104 var assertBlock = fnBind(seleniumApi[functionName], seleniumApi); 105 106 // Register the assert with the "assert" prefix, and halt on failure. 107 var assertName = functionName; 108 this.registerAssert(assertName, assertBlock, true); 109 110 // Register the assert with the "verify" prefix, and do not halt on failure. 111 var verifyName = "verify" + match[1]; 112 this.registerAssert(verifyName, assertBlock, false); 113 } 114 } 115 }, 116 117 registerAll: function(seleniumApi) { 118 this._registerAllAccessors(seleniumApi); 119 this._registerAllActions(seleniumApi); 120 this._registerAllAsserts(seleniumApi); 121 }, 122 123 _predicateForAccessor: function(accessBlock, requiresTarget, isBoolean) { 124 if (isBoolean) { 125 return this._predicateForBooleanAccessor(accessBlock); 126 } 127 if (requiresTarget) { 128 return this._predicateForSingleArgAccessor(accessBlock); 129 } 130 return this._predicateForNoArgAccessor(accessBlock); 131 }, 132 133 _predicateForSingleArgAccessor: function(accessBlock) { 134 // Given an accessor function getBlah(target), 135 // return a "predicate" equivalient to isBlah(target, value) that 136 // is true when the value returned by the accessor matches the specified value. 137 return function(target, value) { 138 var accessorResult = accessBlock(target); 139 accessorResult = selArrayToString(accessorResult); 140 if (PatternMatcher.matches(value, accessorResult)) { 141 return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'"); 142 } else { 143 return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'"); 144 } 145 }; 146 }, 147 148 _predicateForNoArgAccessor: function(accessBlock) { 149 // Given a (no-arg) accessor function getBlah(), 150 // return a "predicate" equivalient to isBlah(value) that 151 // is true when the value returned by the accessor matches the specified value. 152 return function(value) { 153 var accessorResult = accessBlock(); 154 accessorResult = selArrayToString(accessorResult); 155 if (PatternMatcher.matches(value, accessorResult)) { 156 return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'"); 157 } else { 158 return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'"); 159 } 160 }; 161 }, 162 163 _predicateForBooleanAccessor: function(accessBlock) { 164 // Given a boolean accessor function isBlah(), 165 // return a "predicate" equivalient to isBlah() that 166 // returns an appropriate PredicateResult value. 167 return function() { 168 var accessorResult; 169 if (arguments.length > 2) throw new SeleniumError("Too many arguments! " + arguments.length); 170 if (arguments.length == 2) { 171 accessorResult = accessBlock(arguments[0], arguments[1]); 172 } else if (arguments.length == 1) { 173 accessorResult = accessBlock(arguments[0]); 174 } else { 175 accessorResult = accessBlock(); 176 } 177 if (accessorResult) { 178 return new PredicateResult(true, "true"); 179 } else { 180 return new PredicateResult(false, "false"); 181 } 182 }; 183 }, 184 185 _invertPredicate: function(predicateBlock) { 186 // Given a predicate, return the negation of that predicate. 187 // Leaves the message unchanged. 188 // Used to create assertNot, verifyNot, and waitForNot commands. 189 return function(target, value) { 190 var result = predicateBlock(target, value); 191 result.isTrue = !result.isTrue; 192 return result; 193 }; 194 }, 195 196 createAssertionFromPredicate: function(predicateBlock) { 197 // Convert an isBlahBlah(target, value) function into an assertBlahBlah(target, value) function. 198 return function(target, value) { 199 var result = predicateBlock(target, value); 200 if (!result.isTrue) { 201 Assert.fail(result.message); 202 } 203 }; 204 }, 205 206 _invertPredicateName: function(baseName) { 207 var matchResult = /^(.*)Present$/.exec(baseName); 208 if (matchResult != null) { 209 return matchResult[1] + "NotPresent"; 210 } 211 return "Not" + baseName; 212 }, 213 214 _registerAssertionsForPredicate: function(baseName, predicateBlock) { 215 // Register an assertion, a verification, a negative assertion, 216 // and a negative verification based on the specified accessor. 217 var assertBlock = this.createAssertionFromPredicate(predicateBlock); 218 this.registerAssert("assert" + baseName, assertBlock, true); 219 this.registerAssert("verify" + baseName, assertBlock, false); 220 221 var invertedPredicateBlock = this._invertPredicate(predicateBlock); 222 var negativeassertBlock = this.createAssertionFromPredicate(invertedPredicateBlock); 223 this.registerAssert("assert" + this._invertPredicateName(baseName), negativeassertBlock, true); 224 this.registerAssert("verify" + this._invertPredicateName(baseName), negativeassertBlock, false); 225 }, 226 227 _waitForActionForPredicate: function(predicateBlock) { 228 // Convert an isBlahBlah(target, value) function into a waitForBlahBlah(target, value) function. 229 return function(target, value) { 230 var terminationCondition = function () { 231 try { 232 return predicateBlock(target, value).isTrue; 233 } catch (e) { 234 // Treat exceptions as meaning the condition is not yet met. 235 // Useful, for example, for waitForValue when the element has 236 // not even been created yet. 237 // TODO: possibly should rethrow some types of exception. 238 return false; 239 } 240 }; 241 return Selenium.decorateFunctionWithTimeout(terminationCondition, this.defaultTimeout); 242 }; 243 }, 244 245 _registerWaitForCommandsForPredicate: function(seleniumApi, baseName, predicateBlock) { 246 // Register a waitForBlahBlah and waitForNotBlahBlah based on the specified accessor. 247 var waitForActionMethod = this._waitForActionForPredicate(predicateBlock); 248 var waitForActionBlock = fnBind(waitForActionMethod, seleniumApi); 249 250 var invertedPredicateBlock = this._invertPredicate(predicateBlock); 251 var waitForNotActionMethod = this._waitForActionForPredicate(invertedPredicateBlock); 252 var waitForNotActionBlock = fnBind(waitForNotActionMethod, seleniumApi); 253 254 this.registerAction("waitFor" + baseName, waitForActionBlock, false, true); 255 this.registerAction("waitFor" + this._invertPredicateName(baseName), waitForNotActionBlock, false, true); 256 //TODO decide remove "waitForNot.*Present" action name or not 257 //for the back compatiblity issues we still make waitForNot.*Present availble 258 this.registerAction("waitForNot" + baseName, waitForNotActionBlock, false, true); 259 }, 260 261 _registerStoreCommandForAccessor: function(baseName, accessBlock, requiresTarget) { 262 var action; 263 if (requiresTarget) { 264 action = function(target, varName) { 265 storedVars[varName] = accessBlock(target); 266 }; 267 } else { 268 action = function(varName) { 269 storedVars[varName] = accessBlock(); 270 }; 271 } 272 this.registerAction("store" + baseName, action, false, true); 273 } 274 275}); 276 277function PredicateResult(isTrue, message) { 278 this.isTrue = isTrue; 279 this.message = message; 280} 281 282// NOTE: The CommandHandler is effectively an abstract base for 283// various handlers including ActionHandler, AccessorHandler and AssertHandler. 284// Subclasses need to implement an execute(seleniumApi, command) function, 285// where seleniumApi is the Selenium object, and command a SeleniumCommand object. 286function CommandHandler(type, haltOnFailure) { 287 this.type = type; 288 this.haltOnFailure = haltOnFailure; 289} 290 291// An ActionHandler is a command handler that executes the sepcified action, 292// possibly checking for alerts and confirmations (if checkAlerts is set), and 293// possibly waiting for a page load if wait is set. 294function ActionHandler(actionBlock, wait, dontCheckAlerts) { 295 this.actionBlock = actionBlock; 296 CommandHandler.call(this, "action", true); 297 if (wait) { 298 this.wait = true; 299 } 300 // note that dontCheckAlerts could be undefined!!! 301 this.checkAlerts = (dontCheckAlerts) ? false : true; 302} 303ActionHandler.prototype = new CommandHandler; 304ActionHandler.prototype.execute = function(seleniumApi, command) { 305 if (this.checkAlerts && (null == /(Alert|Confirmation)(Not)?Present/.exec(command.command))) { 306 // todo: this conditional logic is ugly 307 seleniumApi.ensureNoUnhandledPopups(); 308 } 309 var terminationCondition = this.actionBlock(command.target, command.value); 310 // If the handler didn't return a wait flag, check to see if the 311 // handler was registered with the wait flag. 312 if (terminationCondition == undefined && this.wait) { 313 terminationCondition = seleniumApi.makePageLoadCondition(); 314 } 315 return new ActionResult(terminationCondition); 316}; 317 318function ActionResult(terminationCondition) { 319 this.terminationCondition = terminationCondition; 320} 321 322function AccessorHandler(accessBlock) { 323 this.accessBlock = accessBlock; 324 CommandHandler.call(this, "accessor", true); 325} 326AccessorHandler.prototype = new CommandHandler; 327AccessorHandler.prototype.execute = function(seleniumApi, command) { 328 var returnValue = this.accessBlock(command.target, command.value); 329 return new AccessorResult(returnValue); 330}; 331 332function AccessorResult(result) { 333 this.result = result; 334} 335 336/** 337 * Handler for assertions and verifications. 338 */ 339function AssertHandler(assertBlock, haltOnFailure) { 340 this.assertBlock = assertBlock; 341 CommandHandler.call(this, "assert", haltOnFailure || false); 342} 343AssertHandler.prototype = new CommandHandler; 344AssertHandler.prototype.execute = function(seleniumApi, command) { 345 var result = new AssertResult(); 346 try { 347 this.assertBlock(command.target, command.value); 348 } catch (e) { 349 // If this is not a AssertionFailedError, or we should haltOnFailure, rethrow. 350 if (!e.isAssertionFailedError) { 351 throw e; 352 } 353 if (this.haltOnFailure) { 354 var error = new SeleniumError(e.failureMessage); 355 throw error; 356 } 357 result.setFailed(e.failureMessage); 358 } 359 return result; 360}; 361 362function AssertResult() { 363 this.passed = true; 364} 365AssertResult.prototype.setFailed = function(message) { 366 this.passed = null; 367 this.failed = true; 368 this.failureMessage = message; 369} 370 371function SeleniumCommand(command, target, value, isBreakpoint) { 372 this.command = command; 373 this.target = target; 374 this.value = value; 375 this.isBreakpoint = isBreakpoint; 376} 377