/** * @class ST.pageobject.Base * A PageObject is a simple class upon which custom methods can be defined to encapsulate * common tasks associated with writing tests via the Sencha Test Futures API. * * ## Anatomy * A custom PageObject class has 2 main requirements: * * ### Class Name * To define a PageObject, you will use the following syntax: * * ST.pageobject.define('MyCustomPageObject', { * type: 'custom' * }); * * In this example, "MyCustomPageObject" is the name of the custom PageObject class we are creating, * it's fully qualified name being: * * ST.pageobject.MyCustomPageObject * * ### Type * The "type" must be a unique key, per project, that identifies the PageObject (and will be used * to register the PageObject with the PageObject Manager). In our example above, "custom" * is the custom type for our PageObject class. * * ### Custom Methods * Beyond the requirements for creating a custom PageObject class, you can additional define * any number of custom methods that encapsulate logic that you can use within your tests in a much less verbose way. * * For example, if we have a loginButton in our page that we wish to locate, we can create a custom method on our * PageObject that returns a Component Future: * * ST.pageobject.define('MyCustomPageObject', { * type: 'custom', * * getLoginButton: function () { * return ST.button('#myLoginButton'); * } * }); * * In this example, the "loginButton" method encapsulates the location of the button component. So now, instead of having * to use the verbose form (ST.button(...)), we can simply use our PageObject's custom methods: * * var myPO = ST.PageObject.get('custom'); * var button = myPO.getLoginButton(); * * Besides locating elements, however, we can additionally use PageObjects to encapsulate more complex functionality. * * ST.pageobject.define('MyCustomPageObject', { * type: 'custom', * * getLoginButton: function () { * return ST.button('#myLoginButton'); * }, * * submitLogin: function () { * var button = this.getLoginButton(); * button.click(); * } * }); * * In this example, we added a new method (submitLogin) which not only uses the other custom method to retrieve the * button Future, but also executes the click() method from the Sencha Test API upon it. There are, of course, much more * complex scenarios that are supported, but hopefully this gives you a good taste of what is possible with PageObjects. */ST.pageobject.Base = ST.define({ type: 'basepageobject', $isPageObject: true, /** * @method getType * Returns the custom type used to uniquely identify this PageObject class * @return {String} */ getType: function () { return this.type || this.name.toLowerCase(); }, /** * @method getName * Returns the short name for this PageObject class (e.g., ST.pageobject.Custom => "Custom") * @return {String} */ getName: function () { return this.name; }}); ST.pageobject._validateLocatorFn = function (def) { var hasLocator = !!def.locator, hasType = !!def.type; return hasLocator && hasType;} ST.pageobject._createLocatorFn = function (cls, key, def) { var type = def.type, locator = def.locator, fn; fn = function () { return ST[type](locator); } cls.prototype[key] = fn;} ST.pageobject.define = function (pageObjectName, body) { if (!body.extend) { // TODO: Do we want to allow extension of page objects? // it's free, but will it cause headaches? body.extend = ST.pageobject.Base; } var locators = body._locators, // by default, page objects will be auto-registered with manager when the class register = typeof body.register !== 'undefined' ? body.register : true; delete body.register; delete body._locators; var cls = ST.define(body), // deletes body.extend parts = pageObjectName.split('.'), methodScope = ST, classScope = ST.pageobject, type, className, locatorDef, locatorKey; while (parts.length > 1) { type = parts.shift(); if (!classScope[type]) { classScope[type] = {}; } if (!methodScope[type]) { methodScope[type] = {}; } } type = parts[0]; className = cls.prototype.$className = 'ST.pageobject.' + pageObjectName; cls.prototype.name = pageObjectName; cls.prototype.type = body.type || pageObjectName.toLowerCase(); if (locators) { for (locatorKey in locators) { locatorDef = locators[locatorKey]; if (ST.pageobject._validateLocatorFn(locatorDef)) { ST.pageobject._createLocatorFn(cls, locatorKey, locatorDef); } } } // add to ST.pageobject namesapce classScope[type] = cls; // register is a hook for testing; normally, page objects will auto-register themselves, // but we can specify register:false in the definition to prevent it and require manual registration if (register) { // register with PageObject Manager ST.PageObject.register(className); } return cls;};