Protractor element caching - angularjs

I started to learn testing in Angular and have a little problem doing a serie of test in the same view.
I have a form with some inputs for customer data. I use it both for add new customer and edit an existing one.
So, firstly I test adding a new customer, then I click back, and click the first customer in a list (not the recently added). The data shows correctly in the view, but the test fails saying Expected 'new customer' to be 'customer 1'. Somehow element.name is caching the previous value.
Here is the test:
describe('Showing Customer data', function(){
var customersMenu, homeMenu, firstListItem;
beforeEach(function() {
customersMenu = element(by.css('.ion-person-stalker'));
homeMenu = element(by.css('.ion-home'));
firstListItem = element(by.css('.firstListItem'));
});
it('should create a new customer', function() {
customersMenu.click();
var newButton = element(by.css('.button-fab'));
newButton.click();
element(by.name('name')).sendKeys('new customer');
[...] // The other fields...
homeMenu.click();
});
it('should display correct customer data', function() {
customersMenu.click();
firstListItem.click();
expect(element(by.name('name')).getAttribute('value')).toBe('customer 1');
homeMenu.click();
});
});

It looks to me like you are sending the keys "new customer" to the name field here:
element(by.name('name')).sendKeys('new customer');
should you not be sending "customer 1" and the value of the name field like this?
element(by.name('name')).sendKeys('customer 1');
then later when you get the value of the name field, this will be correct:
expect(element(by.name('name')).getAttribute('value')).toBe('customer 1');

Finally I achieve this.
The problem
The problem was I was navigating like an user will do. I was returning to home view and then going again to list view, and detail view. The problem with that is the previous elements remain, so element(by.name('name')) has two posible values: 'new customer' and 'customer 1'. I we use element() instead element.all(), Protractor will pick up the first element in array.
The Solution
Instead of returning home clicking the button in sidemenu, now I use browser.get('/') to return to the root of app. It will clear DOM and seems is like you run the test as the first one.

Related

How to programmatically fill values in successive select in AngularJS?

Programmatically
So I am facing an issue with the select in Angular JS. This is a follow-up question to my previous question
Now I am able to load my values in the second select but I am unable to write the same value to it when the query from DB comes back. So If I select Ford in one select and Figo in another. and press save. The values go into DB correctly but when I come again to that view. Shouldn't that value persist right? I mean I should be able to see figo in the second select. But I am unable to. I tried setting to the ng-model but that method doesn't work. I have also attached a fiddle with the proper comments as to what is not working.
The code uses one of the marked answers from the question.
HTML
<select ng-model="carBrand" name="carBrand" required ng-options=" brand for brand in brands" ng-change="selectedCar(carBrand)"></select>
<select ng-model="carModel" name="carModel" required ng-options="model.name for model in cars[carIndex]"></select>
JS
$scope.brands = ['Ford', 'Honda', 'Hyundai', 'Mahindra',
'Maruti Suzuki', 'Nissan', 'Renault', 'Skoda', 'Tata', 'Toyota', 'Volksvagen'
];
$scope.cars[0] = $scope.cars[0] = [{
name: "Figo",
capacity: 45
}, {
name: "Ecosport",
capacity: 52
}, {
name: "Fiesta",
capacity: 45
}, {
name: "Endeavour",
capacity: 71
}];
$scope.carBrand = $scope.brands[0];
$scope.cars = [];
$scope.selectedCar = function(brand) {
$scope.carIndex = $scope.brands.indexOf(brand);
};
$scope.carModel = "Anything";
EDIT
I think there is an issue in the understanding of the question. All I want is programmatically(by code) set the value of the second select. That's it.
I have forked your fiddle. Have a look: https://jsfiddle.net/4ynkrj0o/2/
var MockSaveMyData = function(itemsToSave){
// I JUST SAVED ALL MY DATE !! :D
var savedItems = itemsToSave;
savedItems.CreatedAt = new Date();
savedItems.CreatedBy = 'YOU !!';
return {
then: function(callback){
callback(savedItems);
}
};
};
You will have to save and retrieve the values on every page refresh.
Like you said, you have cookies where you save your Car object. In that case, you will have to assign the object on the top firth thing inside the controller.
You will also have to update the cookies after you have run the save function. I will suggest doing all this in the success call back which http calls provide. You can update the cookie and then refresh the page. Or you can just assign the updated values and give a message to the user stating that the save call has been successful.
Again, in my opinion, you should avoid reloading the page. It almost defies the essence of Angular. Where you can do almost everything asynchronously.

Check if the new element has been added to the table in Protractor

In my Angular app I have a table (ng-grid to be precise) and I have a procedure to add the a new element to it. I spare you the details, but the procedure is multi-step, involves talking to the server and results in a new item in the ng-grid table. I know the name of the new item and I want to check in my protractor test that this item has indeed be added. This is how I'm trying to do it:
var nameTestItem="mySuperItem";
//... some actions to add the element, and going back to the page with the table
browser.get('#/resTable');
//checking if there is new item with this title
var element_added=false;
//picking the cell of the table with css selector and searching for the text
element.all(by.css('div.ui-grid-cell-contents')).then(function(elements) {
_.forEach(elements, function(el) {
el.getText().then(function(text){
console.log(text);
element_added= element_added || (nameTestItem==text);
});
});
});
browser.sleep(1000);
browser.pause();
expect(element_added).toBeTruthy();
apparently my problem is that I'm dealing with a lot of promises. How would you tackle this problem? I really don't want to rely on count() because I don't want collisions when several people execute the test.
I'd use filter() instead:
var results = element.all(by.css('div.ui-grid-cell-contents')).filter(function(elm) {
return elm.getText().then(function(text) {
return nameTestItem === text;
});
});
expect(results.count()).toEqual(1);
And, if you are using jasmine-matchers, a bit more readable way:
expect(results).toBeNonEmptyArray();

Angular. Watch for selected values inside ng-repeat list and pass them to service

I'm new to AngularJS and tried to find solution with no success.
I have dynamic form with nested ng-repeats as custom directives. Here is my structure in plunker.
In directive "rule" i have two selects, one for parameters another one for values.
<rule ng-repeat="rule in cc.route.rules track by rule.id">
<li class="second-level">
{{ $index +1 }}
<select ng-change="ic.getParamName(mc.main.routes[$parent.$index].rules[rule.id].param)"
ng-model="mc.main.routes[$parent.$index].rules[rule.id].param"
ng-options="param.name as param.name for param in ic.params"></select>
<select ng-change="" ng-model="mc.main.routes[$parent.$index].rules[rule.id].value"
ng-options="value.code as value.name for value in ic.values"></select>
<button ng-click="ic.removeRule(rule,$parent.$index)">del</button>
</li>
</rule>
When i select the parameter it fires the function with passing the parameter's name.
In the controller of current directive i catch this parameter by switch case and pass it to the certain method in service. Then i get the list of values for this parameter.
case "Countries":
vm.values = DataService.getCountries();
break;
case "Cities":
DataService.getCities().then(function(data){
vm.values = data;
});
break;
My issue is that I want to get cities only if some country is already selected in previous ng-repeats(rules) of current route (we can catch only the last selected country if there are more than one), and when i'm adding the new rule with cities, pass the code of the country selected above as argument to the certain method in service to make http request and get cities for this country. Same issue with OS and OS versions.
How i can watch for rules in current route and path the codes of countries and OS to the service with adding new rule?
UPDATE:
Let's say we add new route and want there 2 rules with country "USA" and city "Dallas". We need to add new rule to this route, choose the parameter "Countries" in the first select and the value "USA" in the second. Then we want to choose "Dallas", but my server can return american cities only if i pass the country code "US" to http request. So at the moment when we add new rule and choose the parameter "Cities" we need to get all values from all selects above, check if there was country and get it country code, then path it to the service with getCities() method.
Thanks! And please let me know if it is not well understood.
Re-factor the User Interface
Currently as designed the UI has an Add new Rule button that allows the user to add illegal rules. As shown in the screenshot, the user has erroneously added two countries. And as stated by the OP, users can add a Cities rule before a country is selected.
The advice is to change the Add new Rule button to a drop-down menu that has illegal options disabled. When rules are added or removed, the controller should enable or disable appropriate legal items.
The ng-options directive allows a controller to enable and disable options using the disable when clause. For more information, visit AngularJS ng-options API Reference.
The UI should not allow the user to add illegal rules. Also the Submit button should be disabled until the form is complete. Users should be given advice on how to complete the form. "Add Route", "Select Rule", "Select Country", etc.
XHR Request Issues
In the PLNKR the DataService.getCities function has several problems.
//WRONG
function DataService() {
return {
getCities: function (code) {
$http.get('/get_cities?country_code=' + code)
.success(function (response) {
return(response.data);
});
The return statement doesn't return a value to the getCities function. It returns values to the anonymous function inside the .success method and the .success method ignores return values.
//BETTER
function DataService($http, $q) {
return {
getCities: function (code) {
if (isBad(code)) {
return $q.reject("BAD Country Code");
};
//Otherwise send request
var getCitiesPromise = (
$http.get('/get_cities?country_code=' + code)
.then(function onFulfilled (response) {
return(response.data);
});
);
return getCitiesPromise;
}
}
};
In this example, the getCities function checks the country code and returns a rejected promise if the code is bad. If the country code is OK, an XHR request is sent and a promise is return fulfilled with the data or rejected with the $http service error.

extjs 4 form still has record set after form.reset();

I have a grid bound to a form the forms submit action is to update the loaded record if there is one and add a new record if its a blank form. but if I select a record first and then call
myGrid.getSelectionModel().deselectAll();
myform.getForm().reset();
to clear the form so I can add a new record it overwrites the previously selected record with an update.
record = myform.getRecord();
if(record){
record.set(values);
}
shouldn't myform.getRecord(); be null after a reset? how do I clear the record selection?
In short, no, it shouldn't and you don't have legal approaches to clear the record after the first time you load anything via loadRecord.
Although, you could still do myform.getForm()._record = null assignment, I would strongly object against that, as it may break some internal functionality by ExtJS.
Here is an extract from ExtJS API:
getRecord() : Ext.data.Model
Returns the last Ext.data.Model instance
that was loaded via loadRecord
And it does exactly that, returns the last record loaded via loadRecord.
Here are some sources:
getRecord: function() {
return this._record;
},
loadRecord: function(record) {
this._record = record;
return this.setValues(record.data);
},
Actually, those are the only methods of Ext.form.Basic (an instance of which is returned by getForm()) dealing with this._record field.
As for reset
reset: function() {
var me = this;
me.batchLayouts(function() {
me.getFields().each(function(f) {
f.reset();
});
});
return me;
},
As you could see, reset has nothing to do with the record returned by getRecord(), it's just resetting field values.
For anyone interested, you can override the default form panel to add functionality to clear form fields. Add the following to your code:
Ext.override(Ext.form.Panel, {
clearForm:function(){
Ext.each(this.getForm().getFields().items, function(field){
field.setValue('');
});
}
});
You can then clear a form using:
myForm.clearForm()
Where myForm is your form panel.
This is what you looking for:
form.getForm().reset(true);
True to unbind any record set by loadRecord.
See Ext.form.Basic.reset() syntax
I find this question because I have a similar scenario but slightly different:
ExtJS 4.1.1
TreePanel
In ExtJS 4.1 in sync() method you can use an options object in which you can define a callback, success and failure functions. Since I'm sure I'm only synchronozing just one record, I loaded the returned record:
me.getHelpItemsStore().sync({
success: function(batch) {
// We expect single operation so...
var record = batch.operations[0].getRecords()[0];
form.loadRecord(record);
}
});
Little late but hope helps you.

ExtJS: Ext.grid.Panel: how to keep sort order after store.sync()?

I have a nice working Ext.grid.Panel, with column headers you can click on to "automatic" sort.
The store has "autoSync: true".
I have a button "new", when the user clicks on it, it creates an empty record without the id property:
onAddClick: function(){
this.down('#new').setDisabled(true);
var rec = new GroupeSynergies.data.Partenaire({
/* Valeurs par défaut des colonnes */
description: 'Nouveau partenaire'
});
this.store.insert(0, rec);
},
I insert the record in the #0 position, because I know that it'll be automatically synced (and that's what's happening actually).
The problem is: if you click on the "id" column, it's sorted by id asc, if you click again, reverse order.
Then you click on the button "New", it creates empty new record, sends it to the server, and gets the resulting record with the id field completed, updates the grid, but... don't take in account the sort: when it's synced, the returned id is very high and it stays on the top, no matter what the sort order is. What am I doing wrong?
Thank you very much
(PS I'm asking at stackoverflow because Sencha's forum seems to be overwhelmed)
In a similar situation, the solution I've found is to add a store.sort in the 'write' event handler of the store.
Ext.application({
(...)
launch: function() {
var myStore = this.getStore('myStore');
myStore.on('write', function(store, operation){ store.sort('id','ASC') }; );
}
});
Adapt the sorting parameter to your needs. Documentation for the 'sort' method.
If you have a writer set on the proxy of your store, the sorting will also fire for a simple "update". To avoid this situation you can test the value of the operation parameter.
Documentation for the 'write' event.
I don't know if it is the better way of doing it, it's the only one I've found for now.
Here's another solution:
Ext.define('My.data.Store', {
extend: 'Ext.data.Store',
sortOnWrite: false,
resort: function () {
var me = this;
me.doSort(me.generateComparator());
},
onProxyWrite: function (operation) {
var me = this;
if (me.sortOnWrite && operation.wasSuccessful()) {
me.resort();
}
}
});

Resources