I'm using Bryntum Siesta for UI testing an ExtJS app. I've created a TestClass and aim to use its methods for different views. Whole actions of test are same only some specific things are changing such as package, view, grid names. Here is some snippets from Test Suite:
Main Test Class
var isDisplaying = 'Grid is displaying now.';
var collapseDesc = 'Collapse Navbar';
Class('Siesta.Test.ListScreen', {
isa : Siesta.Test.ExtJS,
methods: {
navigation: function (callback) {
var t = this;
t.chain(
{waitForCQ: 'treelist[itemId=navigationTreeList]'},
function (next) {
t.click('treelist[itemId=navigationTreeList]');
next();
},
{click: '>> treelistitem[_text=Package_Name]'},
{click: '>> treelistitem[_text=Package_Submodule]', desc: 'Package Submodule'+isDisplaying},
{click: '#main-navigation-btn => .fa-navicon', desc: collapseDesc},
function (next) {
console.log('navigation func log');
next();
},
callback
)
}
}
});
And this testClass calling from Package_Submodule and getting success:
describe('UI Testing: Submodule List Screen', function (t) {
//Extended method for navigation to submodule
t.it('Should open: Submodule Grid', function (t) {
t.chain(
{
navigation: t.next
}
)
});
});
The thing here is I want to call same TestClass method for another Submodule and override several things such as Package_Name and Package_Submodule. How can i be success to do this?
Thanks in advance
UPDATE through JackSamura's answer:
Dear #SamuraiJack I've refactored the Main Class (ListScreen) and inserted has attribute. As well modified the harness with config property but unfortunately it did not override myPackageName or mySubModule. Instead of i got this error:
Waiting for element ">> treelistitem[_text=packageName]" to appear
As well I've tried to use function arguments but it did not work too. Could you please give an idea why I couldn't override new values?
Main Class (Updated):
var isDisplaying = 'Grid is displaying now.';
var collapseDesc = 'Collapse Navbar';
Class('Siesta.Test.ListScreen', {
isa : Siesta.Test.ExtJS,
has : {
myPackageName : 'packageName',
mySubModule : 'subModule'
},
methods: {
navigation: function (callback) {
var t = this;
t.chain(
{waitForCQ: 'treelist[itemId=navigationTreeList]'},
function (next) {
t.click('treelist[itemId=navigationTreeList]');
next();
},
{click: '>> treelistitem[_text='+this.myPackageName+']'},
{click: '>> treelistitem[_text='+this.mySubModule+']', desc: this.mySubModule+isDisplaying},
{click: '#main-navigation-btn => .fa-navicon', desc: collapseDesc},
function (next) {
console.log('navigation func log');
next();
},
callback
)
}
}
});
index.js:
group: 'UI Tests',
items: [
{
group: 'Submodule List Screen',
testClass: Siesta.Test.ListScreen,
items: [
{
title : 'Submodule1',
hostPageUrl : localApp,
url : '02-ui-tests/02_01-submodule-list-screen/submodule1-list.t.js',
config : {
myPackageName : 'Package1',
mySubModule : 'Submodule1'
}
},
You can do it in 2 ways:
1) Add arguments to the "navigation" method:
// callback should be the last one
navigation: function (packageName, packageSubModule, callback) {
Probably self-explanatory
2) A bit more complex - add new attributes to your custom test class:
Class('Siesta.Test.ListScreen', {
isa : Siesta.Test.ExtJS,
has : {
// values goes into prototype, like in Ext
myPackageName : 'packageName',
mySubModule : 'subModule'
},
methods: {
Then you can refer to those attributes in "navigation" method as usual: this.myPackageName
Then, to override, you can either create a new test class (subclassing Siesta.Test.ListScreen) and re-define the attributes in it, or alternatively, use the config property of the test descriptor:
harness.start(
{
url : 'mytest.t.js',
config : {
myPackageName : 'value1',
mySubModule : 'value2'
}
},
...
)
Hint: To get the answer faster - post it to the Siesta forum: https://www.bryntum.com/forum/viewforum.php?f=20
UPDATE:
The errors you got are probably because "navigation" method is launched from the sub test (every "t.it()" or "t.describe()" section creates a separate "subtest"). Those sub tests won't have the config applied - it is applied only to the top-level test. One solution would be to copy the attribute values:
// in the "methods" of the custom test class
processSubTestConfig : function (config) {
var cfg = this.SUPER(config)
cfg.myPackage = this.myPackage
...
return cfg
},
But that is already advanced Siesta internals coding. Probably just using function arguments will be simpler..
Related
apostrophe-workflow has the following:
public/js/user.js
apos.define('apostrophe-workflow', {
[...]
construct: function(self, options) {
self.locales = options.locales;
self.locale = options.locale;
[...]
I searched quite a while and did not manage to find the reason why this construct method has access to the options object. I tried browserCall but am not sure how to use this properly.
My assets are pushed using pushAsset, too. But they do not have access to the options after apos.create.
Edit: Example scenario:
A simple module that pushes one script to the browser.
module/index.js
construct: function(self, options) {
self.pushAsset('script', 'name', {when: 'always'});
}
And takes one option.
app.js
modules: {
'module': {
option: 'Option'
}
}
The script should use this option on construct.
module/public/js/script.js
apos.define('module-script', {
construct: function(self, options) {
console.log(options.option); // Print 'Option' to console.
}
});
Another module will call apos.create('module-script').
I hope it's clear.
You can solve (at least) this two ways depending on the structure you want.
1. Explicit browser options
You can explicitly pass options to the browser from your modules configuration by wrapping them in a browser object from the root of the module's config.
in lib/modules/layout-widgets/index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Layout',
browser: {
coolArray: [3, 2, 1]
}
}
This will get merged into the options passed to your browser side JS of the module automatically.
then in /lib/modules/layout-widgets/public/js/always.js
apos.define('layout-widgets', {
extend: 'apostrophe-widgets',
construct: function (self, options) {
self.play = function ($widget, data, options) {
console.log(self.options.coolArray);
}
}
});
2. Super'ing getCreateSingletonOptions
If you don't like the syntax of separating your browser options from your main options, you can always override the method responsible for teeing up the browser side module's default options by copying it, invoking it, and adding on to it.
in lib/modules/layout-widgets/index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Layout',
coolArray: [3,2,1],
construct: function(self, options) {
// copy the method
var superGetCreateSingletonOptions = self.getCreateSingletonOptions;
// redefine it
self.getCreateSingletonOptions = function (req) {
// invoke the original method and save the result
var browserOptions = superGetCreateSingletonOptions(req);
// add on to the default results with whatever you want
browserOptions.coolArray = self.options.coolArray;
browserOptions.somethingElse = 'hey this is fun';
return browserOptions;
};
}
};
then, again, in /lib/modules/layout-widgets/public/js/always.js
apos.define('layout-widgets', {
extend: 'apostrophe-widgets',
construct: function (self, options) {
self.play = function ($widget, data, options) {
console.log(self.options.coolArray);
console.log(self.options.somethingElse);
}
}
});
When opening an Aurelia Dialog you usually pass it a viewModel.
This is how I'm currently doing this but it would be better if the path wasn't hard-coded here.
let lookupResponse = await this.dialogService.open(
{
model:
{
configuration: this.LookupConfiguration
caption: 'Select an item'
},
viewModel: 'App/Components/Lookup/LookupDialog'
});
I'd rather be able to reference the viewModel path like a route
let lookupResponse = await this.dialogService.open(
{
model:
{
configuration: this.LookupConfiguration
caption: 'Select an item'
},
viewModel: App.routes.components.lookupdialog
});
If you just add a Routes.js for the components and try to use it you get this error:
Uncaught (in promise) Error: Cannot determine default view strategy
for object.
So what needs to be added for this to work? A custom view strategy of some kind?
You can import the dialogs into your class and use them like this:
import { LookupDialog } from "app/components/lookup/lookup-dialog.ts";
export class Foo {
bar() {
let lookupResponse = await this.dialogService.open(
{
model:
{
configuration: this.LookupConfiguration
caption: 'Select an item'
},
viewModel: LookupDialog
});
}
}
Should we write callbacks/promises for the re-usable methods in Page Object Pattern based testing in Protractor?
For example .. I have the below test code and Page Objects and its working fine without issues. But should I add callbacks for re-usable methods in page class?
describe('This is a test suite for Login cases',function(){
beforeEach(function() {
LoginPage.goHome();
LoginPage.doLogin();
});
afterEach(function() {
LoginPage.doLogout();
});
it('Scenario1_Login_VerifyFirstName',function(){
//Some Test step
});
Page Class:
var Login = {
PageElements: {
emailInput: element(by.css('.email')),
passwordInput: element(by.css('.password')),
loginForm: element(by.css('.form')),
logout: element(by.linkText('LOG OUT'))
},
goHome: function goHome() {
browser.get('/signin');
browser.driver.manage().window().maximize();
},
doLogin: function doLogin() {
this.PageElements.emailInput.sendKeys(UserName);
this.PageElements.passwordInput.sendKeys(Password);
this.PageElements.loginForm.submit();
},
doLogout: function doLogout() {
browser.wait(EC.elementToBeClickable(this.PageElements.profileLink));
this.PageElements.profileLink.click();
this.PageElements.logout.click();
}
};
module.exports = Login;
Yes you can.
By simply returning values or promises:
goHome: function() {
browser.get('/home');
return browser.getTitle();
},
And should resolve them on spec level inside "it" blocks like below:
it('Page should have a title', function() {
expect(Page.goHome()).toEqual('Home Page');
});
I'm trying to extend an existing Durandal router plugin instance already created with the help of RequireJS.
The method map() should be overriden to add extra mapping parameters.
How should I access the original method from the modified one?
index.js
define([ 'durandal/app', 'app/childRouter'], function(
app, childRouter) {
childRouter.map([ {
route : 'details/:id',
moduleId : 'details/index',
}, {
route : 'details/tabs/base',
moduleId : 'details/tabs/base',
} ]);
childRouter.buildNavigationModel();
return {
router : childRouter
};
});
childRouter.js
define([ 'durandal/app', 'plugins/router'], function(
app, router) {
var childRouter = router.createChildRouter();
childRouter._map = childRouter.map;
childRouter.map = function(data) {
data.unshift({
route : [ '', 'grid' ],
moduleId : 'grid/index',
});
childRouter._map(data);//CALLS THE OVERRIDEN METHOD AGAIN INSTEAD OF ORIGINAL
};
return childRouter;
});
If you want to still call the original "map" function, you'll need to store it before you overwrite it. It's a bit "hacky" to replace functions in this way, but if you REALLY had to do it, this will work:
var childRouter = router.createChildRouter();
childRouter.originalMap = childRouter.map; // Save original
childRouter.map = function(data) {
data.unshift({
route : [ '', 'grid' ],
moduleId : 'grid/index',
});
childRouter.originalMap(data); // Call original
};
I've been used to creating dojo AMD modules as part of my rich internet application, using the following structure -
define([
"dojo/_base/declare"
], function(declare, Button){
return declare(null, {
label:"",
constructor: function(label){
this.label = label
}
});
});
Which is fine, though now I need to include some prototype based inheritance in order to extend a Command object for writing functional tests in intern. I am using the following example in - https://theintern.github.io/intern/#writing-functional-test It shows to extend the Command object by writing -
function CustomCommand() {
Command.apply(this, arguments);
}
CustomCommand.prototype = Object.create(Command.prototype);
CustomCommand.prototype.constructor = CustomCommand;
CustomCommand.prototype.login = function (username, password) {
return new this.constructor(this, function () {
return this.parent
.findById('username')
.click()
.type(username)
.end()
.findById('password')
.click()
.type(password)
.end()
.findById('login')
.click()
.end();
});
};
How and where does this get plugged into my original class?
There is no "parent" concept in declare. Inheritance via declare is mixing in properties into the prototype of an object. The first argument of declare is an array of objects to use as base prototypes. The second argument, your object that you pass, becomes the "immediate" (top level) prototype of objects created with the class returned by declare.
Basically, every time you create inheritance with declare, the properties of the parent classes get mixed into this.prototype, meaning they're accessible through this.
var Command = declare(null, {
findById: function() {/* ... */},
click: function() {/* ... */},
type: function() {/* ... */},
end: function() {/* ... */}
});
var CustomCommand = declare([Command], {
click: function()
// -- You can do whatever custom logic you want here specific to CustomCommand --
// `inherited` is a special method that calls the parent's method.
// This line calls the Command class's `click` method and returns the result:
return this.inherited(arguments);
},
login: function(username, password) {
// The prototype of "CustomCommand" contains all the methods defined by "Command".
return this.findById('username')
.click()
.type(username)
.end()
.findById('password')
.click()
.type(password)
.end()
.findById('login')
.click()
.end();
}
});
var customCommandInstance = new CustomCommand();
customCommandInstance.login();
And of course, you would separate these into different modules instead of doing it all in one file, so:
my/Command.js
define(["dojo/_base/declare"], function(declare) {
return declare(null, {
findById: function() {/* ... */},
click: function() {/* ... */},
type: function() {/* ... */},
end: function() {/* ... */}
});
});
my/CustomCommand.js
define(["dojo/_base/declare", "my/Command"], function(declare, Command) {
return declare([Command], {
login: function(username, password) {
// See above...
}
});
});
main.js
require(["my/CustomCommand"], function(CustomCommand) {
var customCommandInstance = new CustomCommand();
customCommandInstance.login();
}