/* * Copyright 2004 ThoughtWorks, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // A naming convention used in this file: // // // - a "seleniumApi" is an instance of the Selenium object, defined in selenium-api.js. // // - a "Method" is an unbound function whose target must be supplied when it's called, ie. // it should be invoked using Function.call() or Function.apply() // // - a "Block" is a function that has been bound to a target object, so can be called invoked directly // (or with a null target) // // - "CommandHandler" is effectively an abstract base for // various handlers including ActionHandler, AccessorHandler and AssertHandler. // Subclasses need to implement an execute(seleniumApi, command) function, // where seleniumApi is the Selenium object, and command a SeleniumCommand object. // // - Handlers will return a "result" object (ActionResult, AccessorResult, AssertResult). // ActionResults may contain a .terminationCondition function which is run by // -executionloop.js after the command is run; we'll run it over and over again // until it returns true or the .terminationCondition throws an exception. // AccessorResults will contain the results of running getter (e.g. getTitle returns // the title as a string). var CommandHandlerFactory = classCreate(); objectExtend(CommandHandlerFactory.prototype, { initialize: function() { this.handlers = {}; }, registerAction: function(name, actionBlock, wait, dontCheckAlertsAndConfirms) { this.handlers[name] = new ActionHandler(actionBlock, wait, dontCheckAlertsAndConfirms); }, registerAccessor: function(name, accessBlock) { this.handlers[name] = new AccessorHandler(accessBlock); }, registerAssert: function(name, assertBlock, haltOnFailure) { this.handlers[name] = new AssertHandler(assertBlock, haltOnFailure); }, getCommandHandler: function(name) { return this.handlers[name]; }, _registerAllAccessors: function(seleniumApi) { // Methods of the form getFoo(target) result in commands: // getFoo, assertFoo, verifyFoo, assertNotFoo, verifyNotFoo // storeFoo, waitForFoo, and waitForNotFoo. for (var functionName in seleniumApi) { var match = /^(get|is)([A-Z].+)$/.exec(functionName); if (match) { var accessMethod = seleniumApi[functionName]; var accessBlock = fnBind(accessMethod, seleniumApi); var baseName = match[2]; var isBoolean = (match[1] == "is"); var requiresTarget = (accessMethod.length == 1); this.registerAccessor(functionName, accessBlock); this._registerStoreCommandForAccessor(baseName, accessBlock, requiresTarget); var predicateBlock = this._predicateForAccessor(accessBlock, requiresTarget, isBoolean); this._registerAssertionsForPredicate(baseName, predicateBlock); this._registerWaitForCommandsForPredicate(seleniumApi, baseName, predicateBlock); } } }, _registerAllActions: function(seleniumApi) { for (var functionName in seleniumApi) { var match = /^do([A-Z].+)$/.exec(functionName); if (match) { var actionName = match[1].lcfirst(); var actionMethod = seleniumApi[functionName]; var dontCheckPopups = actionMethod.dontCheckAlertsAndConfirms; var actionBlock = fnBind(actionMethod, seleniumApi); this.registerAction(actionName, actionBlock, false, dontCheckPopups); this.registerAction(actionName + "AndWait", actionBlock, true, dontCheckPopups); } } }, _registerAllAsserts: function(seleniumApi) { for (var functionName in seleniumApi) { var match = /^assert([A-Z].+)$/.exec(functionName); if (match) { var assertBlock = fnBind(seleniumApi[functionName], seleniumApi); // Register the assert with the "assert" prefix, and halt on failure. var assertName = functionName; this.registerAssert(assertName, assertBlock, true); // Register the assert with the "verify" prefix, and do not halt on failure. var verifyName = "verify" + match[1]; this.registerAssert(verifyName, assertBlock, false); } } }, registerAll: function(seleniumApi) { this._registerAllAccessors(seleniumApi); this._registerAllActions(seleniumApi); this._registerAllAsserts(seleniumApi); }, _predicateForAccessor: function(accessBlock, requiresTarget, isBoolean) { if (isBoolean) { return this._predicateForBooleanAccessor(accessBlock); } if (requiresTarget) { return this._predicateForSingleArgAccessor(accessBlock); } return this._predicateForNoArgAccessor(accessBlock); }, _predicateForSingleArgAccessor: function(accessBlock) { // Given an accessor function getBlah(target), // return a "predicate" equivalient to isBlah(target, value) that // is true when the value returned by the accessor matches the specified value. return function(target, value) { var accessorResult = accessBlock(target); if (PatternMatcher.matches(value, accessorResult)) { return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'"); } else { return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'"); } }; }, _predicateForNoArgAccessor: function(accessBlock) { // Given a (no-arg) accessor function getBlah(), // return a "predicate" equivalient to isBlah(value) that // is true when the value returned by the accessor matches the specified value. return function(value) { var accessorResult = accessBlock(); if (PatternMatcher.matches(value, accessorResult)) { return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'"); } else { return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'"); } }; }, _predicateForBooleanAccessor: function(accessBlock) { // Given a boolean accessor function isBlah(), // return a "predicate" equivalient to isBlah() that // returns an appropriate PredicateResult value. return function() { var accessorResult; if (arguments.length > 2) throw new SeleniumError("Too many arguments! " + arguments.length); if (arguments.length == 2) { accessorResult = accessBlock(arguments[0], arguments[1]); } else if (arguments.length == 1) { accessorResult = accessBlock(arguments[0]); } else { accessorResult = accessBlock(); } if (accessorResult) { return new PredicateResult(true, "true"); } else { return new PredicateResult(false, "false"); } }; }, _invertPredicate: function(predicateBlock) { // Given a predicate, return the negation of that predicate. // Leaves the message unchanged. // Used to create assertNot, verifyNot, and waitForNot commands. return function(target, value) { var result = predicateBlock(target, value); result.isTrue = !result.isTrue; return result; }; }, createAssertionFromPredicate: function(predicateBlock) { // Convert an isBlahBlah(target, value) function into an assertBlahBlah(target, value) function. return function(target, value) { var result = predicateBlock(target, value); if (!result.isTrue) { Assert.fail(result.message); } }; }, _invertPredicateName: function(baseName) { var matchResult = /^(.*)Present$/.exec(baseName); if (matchResult != null) { return matchResult[1] + "NotPresent"; } return "Not" + baseName; }, _registerAssertionsForPredicate: function(baseName, predicateBlock) { // Register an assertion, a verification, a negative assertion, // and a negative verification based on the specified accessor. var assertBlock = this.createAssertionFromPredicate(predicateBlock); this.registerAssert("assert" + baseName, assertBlock, true); this.registerAssert("verify" + baseName, assertBlock, false); var invertedPredicateBlock = this._invertPredicate(predicateBlock); var negativeassertBlock = this.createAssertionFromPredicate(invertedPredicateBlock); this.registerAssert("assert" + this._invertPredicateName(baseName), negativeassertBlock, true); this.registerAssert("verify" + this._invertPredicateName(baseName), negativeassertBlock, false); }, _waitForActionForPredicate: function(predicateBlock) { // Convert an isBlahBlah(target, value) function into a waitForBlahBlah(target, value) function. return function(target, value) { var terminationCondition = function () { try { return predicateBlock(target, value).isTrue; } catch (e) { // Treat exceptions as meaning the condition is not yet met. // Useful, for example, for waitForValue when the element has // not even been created yet. // TODO: possibly should rethrow some types of exception. return false; } }; return Selenium.decorateFunctionWithTimeout(terminationCondition, this.defaultTimeout); }; }, _registerWaitForCommandsForPredicate: function(seleniumApi, baseName, predicateBlock) { // Register a waitForBlahBlah and waitForNotBlahBlah based on the specified accessor. var waitForActionMethod = this._waitForActionForPredicate(predicateBlock); var waitForActionBlock = fnBind(waitForActionMethod, seleniumApi); var invertedPredicateBlock = this._invertPredicate(predicateBlock); var waitForNotActionMethod = this._waitForActionForPredicate(invertedPredicateBlock); var waitForNotActionBlock = fnBind(waitForNotActionMethod, seleniumApi); this.registerAction("waitFor" + baseName, waitForActionBlock, false, true); this.registerAction("waitFor" + this._invertPredicateName(baseName), waitForNotActionBlock, false, true); //TODO decide remove "waitForNot.*Present" action name or not //for the back compatiblity issues we still make waitForNot.*Present availble this.registerAction("waitForNot" + baseName, waitForNotActionBlock, false, true); }, _registerStoreCommandForAccessor: function(baseName, accessBlock, requiresTarget) { var action; if (requiresTarget) { action = function(target, varName) { storedVars[varName] = accessBlock(target); }; } else { action = function(varName) { storedVars[varName] = accessBlock(); }; } this.registerAction("store" + baseName, action, false, true); } }); function PredicateResult(isTrue, message) { this.isTrue = isTrue; this.message = message; } // NOTE: The CommandHandler is effectively an abstract base for // various handlers including ActionHandler, AccessorHandler and AssertHandler. // Subclasses need to implement an execute(seleniumApi, command) function, // where seleniumApi is the Selenium object, and command a SeleniumCommand object. function CommandHandler(type, haltOnFailure) { this.type = type; this.haltOnFailure = haltOnFailure; } // An ActionHandler is a command handler that executes the sepcified action, // possibly checking for alerts and confirmations (if checkAlerts is set), and // possibly waiting for a page load if wait is set. function ActionHandler(actionBlock, wait, dontCheckAlerts) { this.actionBlock = actionBlock; CommandHandler.call(this, "action", true); if (wait) { this.wait = true; } // note that dontCheckAlerts could be undefined!!! this.checkAlerts = (dontCheckAlerts) ? false : true; } ActionHandler.prototype = new CommandHandler; ActionHandler.prototype.execute = function(seleniumApi, command) { if (this.checkAlerts && (null == /(Alert|Confirmation)(Not)?Present/.exec(command.command))) { // todo: this conditional logic is ugly seleniumApi.ensureNoUnhandledPopups(); } var terminationCondition = this.actionBlock(command.target, command.value); // If the handler didn't return a wait flag, check to see if the // handler was registered with the wait flag. if (terminationCondition == undefined && this.wait) { terminationCondition = seleniumApi.makePageLoadCondition(); } return new ActionResult(terminationCondition); }; function ActionResult(terminationCondition) { this.terminationCondition = terminationCondition; } function AccessorHandler(accessBlock) { this.accessBlock = accessBlock; CommandHandler.call(this, "accessor", true); } AccessorHandler.prototype = new CommandHandler; AccessorHandler.prototype.execute = function(seleniumApi, command) { var returnValue = this.accessBlock(command.target, command.value); return new AccessorResult(returnValue); }; function AccessorResult(result) { this.result = result; } /** * Handler for assertions and verifications. */ function AssertHandler(assertBlock, haltOnFailure) { this.assertBlock = assertBlock; CommandHandler.call(this, "assert", haltOnFailure || false); } AssertHandler.prototype = new CommandHandler; AssertHandler.prototype.execute = function(seleniumApi, command) { var result = new AssertResult(); try { this.assertBlock(command.target, command.value); } catch (e) { // If this is not a AssertionFailedError, or we should haltOnFailure, rethrow. if (!e.isAssertionFailedError) { throw e; } if (this.haltOnFailure) { var error = new SeleniumError(e.failureMessage); throw error; } result.setFailed(e.failureMessage); } return result; }; function AssertResult() { this.passed = true; } AssertResult.prototype.setFailed = function(message) { this.passed = null; this.failed = true; this.failureMessage = message; } function SeleniumCommand(command, target, value, isBreakpoint) { this.command = command; this.target = target; this.value = value; this.isBreakpoint = isBreakpoint; }