Project

General

Profile

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

    
40
var CommandHandlerFactory = classCreate();
41
objectExtend(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
            if (PatternMatcher.matches(value, accessorResult)) {
140
                return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'");
141
            } else {
142
                return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'");
143
            }
144
        };
145
    },
146

    
147
    _predicateForNoArgAccessor: function(accessBlock) {
148
        // Given a (no-arg) accessor function getBlah(),
149
        // return a "predicate" equivalient to isBlah(value) that
150
        // is true when the value returned by the accessor matches the specified value.
151
        return function(value) {
152
            var accessorResult = accessBlock();
153
            if (PatternMatcher.matches(value, accessorResult)) {
154
                return new PredicateResult(true, "Actual value '" + accessorResult + "' did match '" + value + "'");
155
            } else {
156
                return new PredicateResult(false, "Actual value '" + accessorResult + "' did not match '" + value + "'");
157
            }
158
        };
159
    },
160

    
161
    _predicateForBooleanAccessor: function(accessBlock) {
162
        // Given a boolean accessor function isBlah(),
163
        // return a "predicate" equivalient to isBlah() that
164
        // returns an appropriate PredicateResult value.
165
        return function() {
166
            var accessorResult;
167
            if (arguments.length > 2) throw new SeleniumError("Too many arguments! " + arguments.length);
168
            if (arguments.length == 2) {
169
                accessorResult = accessBlock(arguments[0], arguments[1]);
170
            } else if (arguments.length == 1) {
171
                accessorResult = accessBlock(arguments[0]);
172
            } else {
173
                accessorResult = accessBlock();
174
            }
175
            if (accessorResult) {
176
                return new PredicateResult(true, "true");
177
            } else {
178
                return new PredicateResult(false, "false");
179
            }
180
        };
181
    },
182

    
183
    _invertPredicate: function(predicateBlock) {
184
        // Given a predicate, return the negation of that predicate.
185
        // Leaves the message unchanged.
186
        // Used to create assertNot, verifyNot, and waitForNot commands.
187
        return function(target, value) {
188
            var result = predicateBlock(target, value);
189
            result.isTrue = !result.isTrue;
190
            return result;
191
        };
192
    },
193

    
194
    createAssertionFromPredicate: function(predicateBlock) {
195
        // Convert an isBlahBlah(target, value) function into an assertBlahBlah(target, value) function.
196
        return function(target, value) {
197
            var result = predicateBlock(target, value);
198
            if (!result.isTrue) {
199
                Assert.fail(result.message);
200
            }
201
        };
202
    },
203

    
204
    _invertPredicateName: function(baseName) {
205
        var matchResult = /^(.*)Present$/.exec(baseName);
206
        if (matchResult != null) {
207
            return matchResult[1] + "NotPresent";
208
        }
209
        return "Not" + baseName;
210
    },
211

    
212
    _registerAssertionsForPredicate: function(baseName, predicateBlock) {
213
        // Register an assertion, a verification, a negative assertion,
214
        // and a negative verification based on the specified accessor.
215
        var assertBlock = this.createAssertionFromPredicate(predicateBlock);
216
        this.registerAssert("assert" + baseName, assertBlock, true);
217
        this.registerAssert("verify" + baseName, assertBlock, false);
218

    
219
        var invertedPredicateBlock = this._invertPredicate(predicateBlock);
220
        var negativeassertBlock = this.createAssertionFromPredicate(invertedPredicateBlock);
221
        this.registerAssert("assert" + this._invertPredicateName(baseName), negativeassertBlock, true);
222
        this.registerAssert("verify" + this._invertPredicateName(baseName), negativeassertBlock, false);
223
    },
224

    
225
    _waitForActionForPredicate: function(predicateBlock) {
226
        // Convert an isBlahBlah(target, value) function into a waitForBlahBlah(target, value) function.
227
        return function(target, value) {
228
            var terminationCondition = function () {
229
                try {
230
                    return predicateBlock(target, value).isTrue;
231
                } catch (e) {
232
                    // Treat exceptions as meaning the condition is not yet met.
233
                    // Useful, for example, for waitForValue when the element has
234
                    // not even been created yet.
235
                    // TODO: possibly should rethrow some types of exception.
236
                    return false;
237
                }
238
            };
239
            return Selenium.decorateFunctionWithTimeout(terminationCondition, this.defaultTimeout);
240
        };
241
    },
242

    
243
    _registerWaitForCommandsForPredicate: function(seleniumApi, baseName, predicateBlock) {
244
        // Register a waitForBlahBlah and waitForNotBlahBlah based on the specified accessor.
245
        var waitForActionMethod = this._waitForActionForPredicate(predicateBlock);
246
        var waitForActionBlock = fnBind(waitForActionMethod, seleniumApi);
247
        
248
        var invertedPredicateBlock = this._invertPredicate(predicateBlock);
249
        var waitForNotActionMethod = this._waitForActionForPredicate(invertedPredicateBlock);
250
        var waitForNotActionBlock = fnBind(waitForNotActionMethod, seleniumApi);
251
        
252
        this.registerAction("waitFor" + baseName, waitForActionBlock, false, true);
253
        this.registerAction("waitFor" + this._invertPredicateName(baseName), waitForNotActionBlock, false, true);
254
        //TODO decide remove "waitForNot.*Present" action name or not
255
        //for the back compatiblity issues we still make waitForNot.*Present availble
256
        this.registerAction("waitForNot" + baseName, waitForNotActionBlock, false, true);
257
    },
258

    
259
    _registerStoreCommandForAccessor: function(baseName, accessBlock, requiresTarget) {
260
        var action;
261
        if (requiresTarget) {
262
            action = function(target, varName) {
263
                storedVars[varName] = accessBlock(target);
264
            };
265
        } else {
266
            action = function(varName) {
267
                storedVars[varName] = accessBlock();
268
            };
269
        }
270
        this.registerAction("store" + baseName, action, false, true);
271
    }
272

    
273
});
274

    
275
function PredicateResult(isTrue, message) {
276
    this.isTrue = isTrue;
277
    this.message = message;
278
}
279

    
280
// NOTE: The CommandHandler is effectively an abstract base for
281
// various handlers including ActionHandler, AccessorHandler and AssertHandler.
282
// Subclasses need to implement an execute(seleniumApi, command) function,
283
// where seleniumApi is the Selenium object, and command a SeleniumCommand object.
284
function CommandHandler(type, haltOnFailure) {
285
    this.type = type;
286
    this.haltOnFailure = haltOnFailure;
287
}
288

    
289
// An ActionHandler is a command handler that executes the sepcified action,
290
// possibly checking for alerts and confirmations (if checkAlerts is set), and
291
// possibly waiting for a page load if wait is set.
292
function ActionHandler(actionBlock, wait, dontCheckAlerts) {
293
    this.actionBlock = actionBlock;
294
    CommandHandler.call(this, "action", true);
295
    if (wait) {
296
        this.wait = true;
297
    }
298
    // note that dontCheckAlerts could be undefined!!!
299
    this.checkAlerts = (dontCheckAlerts) ? false : true;
300
}
301
ActionHandler.prototype = new CommandHandler;
302
ActionHandler.prototype.execute = function(seleniumApi, command) {
303
    if (this.checkAlerts && (null == /(Alert|Confirmation)(Not)?Present/.exec(command.command))) {
304
        // todo: this conditional logic is ugly
305
        seleniumApi.ensureNoUnhandledPopups();
306
    }
307
    var terminationCondition = this.actionBlock(command.target, command.value);
308
    // If the handler didn't return a wait flag, check to see if the
309
    // handler was registered with the wait flag.
310
    if (terminationCondition == undefined && this.wait) {
311
        terminationCondition = seleniumApi.makePageLoadCondition();
312
    }
313
    return new ActionResult(terminationCondition);
314
};
315

    
316
function ActionResult(terminationCondition) {
317
    this.terminationCondition = terminationCondition;
318
}
319

    
320
function AccessorHandler(accessBlock) {
321
    this.accessBlock = accessBlock;
322
    CommandHandler.call(this, "accessor", true);
323
}
324
AccessorHandler.prototype = new CommandHandler;
325
AccessorHandler.prototype.execute = function(seleniumApi, command) {
326
    var returnValue = this.accessBlock(command.target, command.value);
327
    return new AccessorResult(returnValue);
328
};
329

    
330
function AccessorResult(result) {
331
    this.result = result;
332
}
333

    
334
/**
335
 * Handler for assertions and verifications.
336
 */
337
function AssertHandler(assertBlock, haltOnFailure) {
338
    this.assertBlock = assertBlock;
339
    CommandHandler.call(this, "assert", haltOnFailure || false);
340
}
341
AssertHandler.prototype = new CommandHandler;
342
AssertHandler.prototype.execute = function(seleniumApi, command) {
343
    var result = new AssertResult();
344
    try {
345
        this.assertBlock(command.target, command.value);
346
    } catch (e) {
347
        // If this is not a AssertionFailedError, or we should haltOnFailure, rethrow.
348
        if (!e.isAssertionFailedError) {
349
            throw e;
350
        }
351
        if (this.haltOnFailure) {
352
            var error = new SeleniumError(e.failureMessage);
353
            throw error;
354
        }
355
        result.setFailed(e.failureMessage);
356
    }
357
    return result;
358
};
359

    
360
function AssertResult() {
361
    this.passed = true;
362
}
363
AssertResult.prototype.setFailed = function(message) {
364
    this.passed = null;
365
    this.failed = true;
366
    this.failureMessage = message;
367
}
368

    
369
function SeleniumCommand(command, target, value, isBreakpoint) {
370
    this.command = command;
371
    this.target = target;
372
    this.value = value;
373
    this.isBreakpoint = isBreakpoint;
374
}
375

    
(13-13/20)