how to put verification in pageobject model in protractor - protractor

I have a code (credit to #kishanpatel) Traverse-through-each-row-for-a-column-text which will verify whether the value is added in grid or not. i want to put this in my page object. i was thinking to add the elements into page object and the if condition in a different helper file similar to selenium but i am not sure is that the right appraoch. see the details below.
if I call the mo.helper in spec.ts, it says gridcheck.ispresent() is not a function. How to handle this scenario?
code:
it('verify the grid that master obligation is added', function () {
var testvar = "'test_protractor'";
var row_check = element(by.xpath("//div[contains(text()," + testvar + ")]"));
if (row_check.isPresent()) {
row_check.getText().then(function (msg) {
if (row_check.isPresent()) {
console.log("Grid contains========== " + msg);
}
});
}
});
i have the below method in mo.ts(page object page):
this.grid = function (value) {
// var testvar = "'test_protractor'";
var row_check = element(by.xpath("//div[contains(text()," + value + ")]"));
return require('./mohelper.ts')
}
}
mohelper.ts:
require('../page/mo.ts')
var mohelper = function () {
this.gridvaluepresent = function () {
require('../page/mo.ts')
var gridcheck = mo.grid();
if(gridcheck.isPresent()) {
gridcheck.getText().then(function (msg) {
if (gridcheck.isPresent()) {
console.log("Grid contains========== " + msg);
}
})
}
}
}
module.exports = new mohelper();
spec.ts:
it('go to corresponding module and verify whether the master obligation is added ', function () {
browser.sleep(10000);
taxhome.selectmodule;
taxhome.selectmoduledropdown(1);
mo.grid("test_protractor");
mohelper.gridvaluepresent();
});

Couple of things here to be considered -
1) Most of the protractor's api methods are asynchronous i.e. they return promises you have to resolve/reject them to perform actions.
isPresent() also returns a promise, you need to resolve it-
var row_check = element(by.xpath("//div[contains(text()," + value + ")]"));
row_check.isPresent().then(function(present) {
if(present) { // it returns a boolean value
row_check.getText().then(function (msg) {
console.log("Grid contains========== " + msg);
});
}
});
2) Since you are using TypeScript , use its syntax rather than conventional js-
let row_check = element(by.xpath("//div[contains(text()," + value + ")]")); // Block scoped variable using 'let'
row_check.isPresent().then((present) => { // notice the thick arrow
if(present) {
row_check.getText().then((msg) => {
console.log("Grid contains========== " + msg);
});
}
});
3) Maintain Page Objects efficiently and readable-
All the helper methods, elements etc. for a single page should go in a single page object. Write them in separate classes, typescript uses the concept of classes and transpiles them to global functions.
moHelper.ts
import {ElementFinder, element} from 'protractor';
export class MoHelper {
public row_check: ElementFinder; // its of element finder type
gridValueCheck(value : string) {
row_check = element(by.xpath("//div[contains(text()," + value + ")]")); // please use Css selectors instead of Xpath!
row_check.isPresent().then((present) => {
if(present) {
row_check.getText().then((msg) => {
return msg; // here you are returning the msg of the row from your page!
});
}
});
}
}
Your spec.ts should validate that row msg!
import {MoHelper} from './moHelper.ts'
let mo: MoHelper = new MoHelper();
it('go to corresponding module and verify whether the master obligation is added ', () => {
browser.sleep(10000); // please refrain from using sleeps instead use Expected Conditions
taxhome.selectmodule;
taxhome.selectmoduledropdown(1);
expect(mo.gridValueCheck("test_protractor")).toEqual("Your Expected Message");
});
Please find the links for your reference to understand the above in more detail-
isPresent
Getting started with typescript
Using page objects in protractor/style guide
Expected Conditions

Related

AG Grid: Better way for validation row - valueSetter?

Is there a better way to validate a row in ag-grid than with valueSetter?
I can achieve the validation with that but I am not sure, if there is a better way.
https://www.ag-grid.com/javascript-grid-value-setters/#properties-for-setters-and-parsers
I want to validate two fields in the row. DateFrom and DateUntil (they are not allow to be null and DateFrom must be lower than DateUntil).
There are two ways of possible validation handling:
First: via ValueSetter function
and
Second: via custom cellEditor component
I suggest that it would be better to split the logic between custom components, but as you said you need to validate two cell-values between themselves.
On this case from UI perspective you could try to combine them inside one cell and it would be easily to work with values via one component only.
You could override the valueSetter and call the grid api transaction update instead.
Here is pseudo-code that shows how you could implement this.
valueSetter: params => {
validate(params.newValue, onSuccess, onFail);
return false;
};
validate = (newvalue, success, fail) => {
if (isValid(newValue)) {
success();
} else {
fail();
}
};
onSuccess = () => {
// do transaction update to update the cell with the new value
};
onFail = () => {
// update some meta data property that highlights the cell signalling that the value has failed to validate
};
This way you can also do asynchronous validation.
Here is a real example of an async value setter that has success, failure, and while validating handlers that do transaction updates when validation is done.
const asyncValidator = (
newValue,
validateFn,
onWhileValidating,
onSuccess,
_onFail
) => {
onWhileValidating();
setTimeout(function() {
if (validateFn(newValue)) {
onSuccess();
} else {
_onFail();
}
}, 1000);
};
const _onWhileValidating = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: true
};
params.api.applyTransaction({ update: [data] });
};
const _onSuccess = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: false,
lastValidation: true,
value: params.newValue
};
params.api.applyTransaction({ update: [data] });
};
const _onFail = params => () => {
let data = params.data;
let field = params.colDef.field;
data[field] = {
...data[field],
isValidating: false,
lastValidation: params.newValue
};
params.api.applyTransaction({ update: [data] });
};
const asyncValidateValueSetter = validateFn => params => {
asyncValidator(
params.newValue,
validateFn,
_onWhileValidating(params),
_onSuccess(params),
_onFail(params)
);
return false;
};
Here is a code runner example showing this in action: https://stackblitz.com/edit/async-validation-ag-grid-final
Have a look at this two snippets, these come from our internal knowledge base (accessible to customers)
When editing a value in column 'A (Required)', you will see that it does not allow you to leave it empty. If you leave it empty and return the edit, it will be cancelled.
//Force Cell to require a value when finished editing
https://plnkr.co/edit/GFgb4v7P8YCW1PxJwGTx?p=preview
In this example, we are using a Custom Cell Editor that will also validate the values against a 6 character length rule. While editing, if the value is modified outside of 6 characters, it will appear in red, and when you stop editing the row, the value would be reset, so it only accepts a complete edit if the value is valid.
//Inline Validation while editing a cell
https://plnkr.co/edit/dAAU8yLMnR8dm4vNEa9T?p=preview

When will bindElement call the real service?

Main.view.xml:
<ObjectHeader
id="objectHeader"
title="{name}"
intro="{id}">
<headerContainer>
<IconTabBar
id="tabBar"
items="{
path: 'assigned',
parameters: {expand: 'b, c'}
}"
select="onTabBarSelect">
</IconTabBar>
</headerContainer>
</ObjectHeader>
Main.controller.js
onMasterSelectionChange : function (sPath) {
var oSource = oEvent.getParameter("listItem") || oEvent.getSource(),
// sPath = "/ASet('d')"
sPath = oSource.getBindingContext().getPath(),
oObjectHeader = this.byId("objectHeader");
oObjectHeader.bindElement(sPath);
this._oModel.attachEventOnce("requestCompleted", function(oEvent) {
var oTabBar = this.byId("TabBar");
oFirstListItem = oTabBar.getItems()[0],
sObjectId = oFirstListItem.getBindingContext().getProperty("id"),
sDPath = "/DSet('" + sObjectId + "')";
this._getControls(sDPath);
}, this);
}
I found a strange behavior: when I change the master list selection, sometimes Network will show a "/ASet/assigned" call, but sometimes there will be no call, which cause requestCompleted not fired.
According to: SAPUI5 - bindElement doesn't work the second time,
If you call bindElement with the same path twice, the second time won't actually trigger a new call to get new data, since the path didn't change.
But I am sure it is different path. Is there any way that can trigger this call?
BTW
oObjectHeader.bindElement({
path : sPath,
events: {
change : function (oData, test) {
},
dataRequested : function (oData) {
},
dataReceived: function (oData) {
}
}
});
change can be fired every time I click master list, but dataRequested and dataReceived is never called.
I know this is an old post but you can actually trigger a network call for the bindElement by refreshing the binding within its change event.
this.getView().bindElement({
path: "model>/" + sContextPath,
events: {
change: function (oEvent) {
// Get the context binding object
var oContextBinding = oEvent.getSource();
// Refresh the binding.
// This triggers a network call.
oContextBinding.refresh(false);
}.bind(this)
}
});
Refreshing the binding, triggers the network call. Also, set the bForceUpdate flag of the refresh to false so that it would only update the UI if there is a change in the data as mentioned in the APIs.
If your ODataModel has already loaded the data under sPath (check in myODataModel.oData) it will not load that data again. Therefore dataRequested/dataReceived will not fire.
But change should fire whenever the path changes no matter if data existed or not.
I find a work around by replace bindElement by change&oModel.read ...
onMasterSelectionChange : function (sPath) {
var oSource = oEvent.getParameter("listItem") || oEvent.getSource(),
// sPath = "/ASet('d')"
sPath = oSource.getBindingContext().getPath(),
oObjectHeader = this.byId("objectHeader");
oObjectHeader.bindElement({
path : sPath,
events: {
change : this._onBindingChange.bind(this, sPath)
}
})
},
_onBindingChange : function(sPath) {
this._oModel.read(sPath + "/assigned", {
urlParameters: {
"$expand": "b, c"
},
success: function(oData) {
var aAssigmentSet = oData.results,
oViewModel = this.getModel("viewModel"),
oTabBar = this.byId("hazardTabBar");
oViewModel.setProperty("/aAssigmentSet", aAssigmentSet);
oTabBar.bindAggregation("items",
"viewModel>/aAssigmentSet",
new sap.m.IconTabFilter({
key: "{viewModel>id}",
tooltip : "{viewModel>id}",
text : {
parts: [{path: "viewModel>b/name"},
{path: "viewModel>c/type"}],
formatter : function(name, type) {
var sType = type.split(".");
return name + " (" + sType[sType.length - 1] + ")";
}
}
})
);
if (oTabBar.getItems().length > 0) {
var sSelectedTabKey;
sSelectedTabKey = oTabBar.getItems()[0].getKey();
oTabBar.fireSelect({selectedKey: sSelectedTabKey});
}
}.bind(this),
error: this.errorCallback.bind(this)
});
},
onTabBarSelect : function(oEvent) {
var sRiskAssignmentPath = "/AssignmentSet('" + oEvent.getParameter("selectedKey") + "')";
this._oModel.read(AssignmentSet + "/assignedAssignedControls", {
...
});
}
I think the propper way to achieve this would be to use the invalidate-Method form Odata-Model.
This invalidates all cached entries and forces a new read for the Bindings:
https://sapui5.hana.ondemand.com/#/api/sap.ui.model.odata.v2.ODataModel%23methods/invalidate

SAPUI5 oData POST 500 error

I'm trying to do a oData create on checkbox pressed and getting the following errors. Not sure if this is front end or a back end ABAP issue as have got this same function working in another project.
It is failing on the create part but strangely is still passing through the details for SiteId, ArticleNumber, VarianceDate & Confirmed.
// Set CheckBox status, X for true, blank for false
onVarianceChecked: function (oEvent) {
var oEntry = {};
var bindingContext = oEvent.getSource().getBindingContext(this.MODEL_VIEW);
var path = bindingContext.getPath();
var object = bindingContext.getModel("SI").getProperty(path);
// Pass in the Header fields
oEntry.SiteId = this.SiteId;
oEntry.ArticleNumber = object.ArticleNumber;
oEntry.VarianceDate = moment(new Date(object.VarianceDate)).format('YYYY-MM-DDTHH:mm:ss');
// Set X or blank
if (oEvent.getParameter("selected") === true) {
oEntry.Confirmed = "X";
} else {
oEntry.Confirmed = "";
}
// Do the create
var oModel = this.getView().getModel("SI");
oModel.create("/VarianceHeaderSet", oEntry, {
success: function () {
console.log("Variance confirmed");
MessageToast.show("Variance confirmed", {
duration: 1000
});
},
error: function (oError) {
console.log("Error, variance could not be confirmed");
MessageToast.show("Error, variance could not be confirmed", {
duration: 1000
});
}
});
}
'000000000' is the initial value for Edm.DateTime, hence it will fail when you have modelled a DateTime property to not be nullable.
Go to SEGW and change the property to "nullable" or make sure that you always provide a correct Date in the POST.

Is there a way to resolve multiple promises with Protractor?

I have this:
element(by.id('x')).sendKeys('xxx').then(function(text) {
element(by.id('y')).sendKeys('yyy').then(function(text) {
element(by.id('z')).sendKeys('zzz').then(function(text) {
expect(element(by.id('myButton')).isEnabled()).toBe(true);
})
});
});
The button 'myButton' is enabled when the elements 'x', 'y' and 'z' all have values. It's my understanding that sendKeys returns a promise.
So is this the only way that I can check if 'myButton' which depends on data in all the three fields is enabled?
You don't need to chain any promises because protractor will wait until all the statements are done: https://github.com/angular/protractor/blob/master/docs/control-flow.md
element(by.id('x')).sendKeys('xxx');
element(by.id('y')).sendKeys('yyy');
element(by.id('z')).sendKeys('zzz');
expect(element(by.id('myButton'));
If you want to resolve multiple promises use:
var webdriver = require('selenium-webdriver');
webdriver.promise.fullyResolved(promises);
For example: https://github.com/angular/protractor/blob/d15d35a82a5a2/lib/protractor.js#L327
this is a bit after the fact, but:
var x = element(by.id('x')).sendKeys('xxx');
var y = element(by.id('y')).sendKeys('yyy');
var z = element(by.id('z')).sendKeys('zzz');
myFun(x,y,z).then(function(){
expect(element(by.id('myButton')).isEnabled()).toBe(true);
});
// in a common function library
function myFun(Xel,Yel,Zel) {
return protractor.promise.all([Xel,Yel,Zel]).then(function(results){
var xText = results[0];
var yText = results[1];
var zText = results[2];
});
}
but an even better way:
var x = element(by.id('x')).sendKeys('xxx');
var y = element(by.id('y')).sendKeys('yyy');
var z = element(by.id('z')).sendKeys('zzz');
myFun(x,y,z);
//isEnabled() is contained in the expect() function, so it'll wait for
// myFun() promise to be fulfilled
expect(element(by.id('myButton')).isEnabled()).toBe(true);
// in a common function library
function myFun(Xel,Yel,Zel) {
return protractor.promise.all([Xel,Yel,Zel]).then(function(results){
var xText = results[0];
var yText = results[1];
var zText = results[2];
});
}
another way is to chain the .thens together:
element(by.id('x')).sendKeys('xxx').
then(function(xtext){
element(by.id('y')).sendKeys('yyy');
}).then(function(ytext){
element(by.id('z')).sendKeys('zzz');
}).then(function(ztext){
expect(element(by.id('myButton')).isEnabled()).toBe(true);
});
it seems protractor supports all - protractor.promise.all
read more at:
https://github.com/angular/protractor/issues/2062#issuecomment-94030055
describe('promise.all', function() {
it('should greet the named user', function() {
browser.get('http://juliemr.github.io/protractor-demo');
$('div').click().then(function () {
return protractor.promise.all([
$('h3').getText(),
$('h4').getText()
]);
}).then(function (params) {
console.log('A');
});
});
it('does something else', function() {
console.log('B');
});
If you want to return an object instead of a list, seems you can also do that - used it and it's awesome
element.all(by.css('.fc-event-inner')).map(function(el) {
return {
time: el.findElement(by.className('fc-event-time')).getText(),
title: el.findElement(by.className('fc-event-title')).getText()
}
});
See the properties are actually promises.. protractor will resolve them.

How to globally add a custom locator to Protractor?

I wrote a custom locator for Protractor that finds anchor elements by their ui-sref value. In my specs I just used by.addLocator to add the custom locator, but I figured this might be a cool thing to publish and have other people use it.
The goal is to add this custom locator to the global Protractor object so it can be used in any of your specs.
My initial approach was to add this functionality in the onPrepare block of the Protractor config. Something like the pseudocode below:
onPrepare: function () {
require('ui-sref-locator')(protractor); // The protractor object is available here.
}
That require statement would just execute this function:
function (ptorInstance) {
ptorInstance.by.addLocator('uiSref', function (toState, opt_parentElement) {
var using = opt_parentElement || document;
var possibleAnchors = using.querySelectorAll('a[ui-sref="' + toState +'"]');
var result = undefined;
if (possibleAnchors.length === 0) {
result = null;
} else if (possibleAnchors.length === 1) {
result = possibleAnchors[0];
} else {
result = possibleAnchors;
}
return result;
});
};
The problem is that by is not defined on the protractor object available in the onPrepare block. This means that I cannot use the .addLocator method.
Try the following:
function () {
by.addLocator('uiSref', function (toState, opt_parentElement) {
...
By should be in the global scope.
The protractor object passed to the onPrepare block has a By property. That By property has an inherited enumerable property named addLocator. My understanding of JavaScript is pretty shallow so it really threw me off that when I console.log'ed the protractor.By it returned {}, but if I did for (var propName in protractor.By) it would show me all the "hidden" properties. I'm still struggling to understand that bit.
Working code:
onPrepare: function () {
require('ui-sref-locator')(protractor); // The protractor object is available here.
}
The require would execute the function below:
function (ptor) {
ptor.By.addLocator('linkUiSref', function (toState, opt_parentElement) {
var using = opt_parentElement || document;
var possibleAnchors = using.querySelectorAll('a[ui-sref="' + toState +'"]');
var result = undefined;
if (possibleAnchors.length === 0) {
result = null;
} else if (possibleAnchors.length === 1) {
result = possibleAnchors[0];
} else {
result = possibleAnchors;
}
return result;
});
};