AngularJS e2e Testing: How To Get value of repeater().count()? - angularjs

Problem
Calling repeater('#myTable tr','Rows').count(); returns a Future, not an integer. I need to get the integer value so I can confirm that an additional row was added to a table.
Code
it('should add a new user when save button is clicked',function()
{
showModal();
//here I'm trynig to store the row count of my table into a local variable.
//a future is returned who's 'value' field is undefined.
var memberCount = repeater('#memberTable tr','Member Rows').count();
//this outputs 'undefined'
console.log(memberCount.value);
input('editedMember.name').enter('John');
input('editedMember.grade').enter(5);
input('editedMember.ladderPosition').enter(3);
element('#saveMemberButton').click();
sleep(1);
expect(element(modalId).css('display')).toBe('none');
//here is where I want to do the comparison against the above stored memberCount
expect(repeater('#memberTable tr', 'Member Rows').count()).toBe(memberCount.value + 1);
});
Test Result
Chrome 25.0 e2e should add a new user when save button is clicked FAILED
expect repeater 'Member Rows ( #memberTable tr )' count toBe null
/Users/jgordon/learning/chessClub/web-app/test/e2e/scenarios.js:45:3: expected null but was 6
Chrome 25.0: Executed 2 of 2 (1 FAILED) (1 min 4.117 secs / 1 min 3.773 secs)

Drilling into the source code for Angularjs' e2e support reveals that you have to call execute() on the Future to have it populate its value. Also, when you call execute you have to provide a "done" function to the execute() otherwise Testacular will (oddly enough!) skip your test.
Code
var rowCountFuture = repeater('#memberTable tr','Member Rows').count();
rowCountFuture.execute(function(){
});
var memberCount = rowCountFuture.value;
While I'm jazzed to see this works, I'm concerned there may be some asynchronous bugs that could come out of this, also, I feel like this is a hack and not the right way to do it. Any ideas?

Based on the latest Protractor version:
it('should add a new user when save button is clicked', function() {
var memberCount;
element.all(by.repeater('#memberTable tr','Member Rows')).count().then(function(value) {
memberCount = value;
});
...
// then do all your entering user info, saving etc.
...
browser.refresh(); // or however you want to load new data
expect(element.all(by.repeater('#memberTable tr','Member Rows')).count()).toEqual(memberCount + 1);
});

I've run into the same issue, and have seen confusing results when testing value returned after calling execute(). I've found this method works more reliably:
var getCount = repeater('ul li').count();
getCount.execute(function(value) {
expect(value).toEqual(3);
});

You can do this most easily in the async promise returned by the locator
element.all(By.repeater 'thing in things').then(function(elements){
count = elements.length;
expect(count).toEqual(3);
});

Related

Please I am trying to concatenate string result to a variable in an express router but it returns an empty string

I am trying to iterate through the array get the value and search the database, then concatenate the database result to the string translation
app.get('/translate',function(req,res) {
let translate = '';
['hello','love'].forEach(async (word) => {
let trans = await
NaijaLang.find({"engword": word, "naijalang": "yoruba"});
translate +=" " + trans[0].translation;
//Returns values
console.log(translate)
});
//Returns Empty String;
console.log(translate)
res.send(translate);
});
Because you do some async stuff there, but you send the value synchronously. Basically, this code will run in this order:
run let translate='';
run ['hello','love'].forEach(...)
run await NaijaLang.find(...) asynchronously for word=hello
run await NaijaLang.find(...) asynchronously for word=love
run console.log(translate) and res.send(translate);
resolve the value of await NaijaLang.find(...) -> this is the time when the translate is updated for the first time (either for word=hello or word=love. Whatever finishes earlier)
resolve the value of second call await NaijaLang.find(...) -> this is the time when the translate is updated for the second time. But the value was already send in the 5th step.
You can find more detailed explanation here: https://blog.lavrton.com/javascript-loops-how-to-handle-async-await-6252dd3c795
And you can also find there how to fix it. You can use the for-of loop instead of forEach:
app.get('/translate',function(req,res){
let translate='';
for (let word of ['hello','love']) {
let trans=await NaijaLang.find({"engword":word,"naijalang":"yoruba"});
translate+=" " + trans[0].translation;
//Returns values
console.log(translate)
}
//Returns Empty String;
console.log(translate)
res.send(translate);
});
This time, the code will execute as you probably want. First, the find method will be called for word=hello, then, after the execution is finished, the find method will be called for word=love and finally after both calls are finished, the res.send will be called.

Protractor returning a value from a promise

I'm writing a test to see if my code is removing a level by looking at a text value on the screen which holds the count of levels.
it 'allows deleting level versions', ->
browser.get('/api#/costings')
element(By.id("edit")).click()
startCount = element(By.id("versions_count")).getText().then( (count) ->
return count
)
element(By.id("versions")).click()
first=element.all(By.id("listing")).first()
first.element(By.id("delete")).click()
helper.accept_dialog()
element(By.id("back")).click()
expect(element(By.id("versions_count")).getText()).toEqual(startCount - 1)
Problem here is startCount results in a function. I cannot seem to get startCount into an integer so that I can see if the count has gone down by 1 item.
It gives me the error;
1) edit an existing costing allows deleting level versions
Message:
Expected '1' to equal NaN.
If I try parseInt(startCount) I get the same error.
The variable startCount is a promise, and so startCount - 1 doesn't make sense: there is no automatic type conversion from a promise to its resolved value, so you can't subtract one from it.
What you can do, is create a promise whose resolved value is the expected versions count:
expectedCount = element(By.id("versions_count")).getText().then( (count) ->
return (count - 1).toString();
)
and then you can pass this promise to toEqual, as it automatically unwraps promises at the appropriate point in the control flow
expect(element(By.id("versions_count")).getText()).toEqual(expectedCount)
it("Verify Add Element", function(){
basePage.gotoAssetsPage();
var numberOfElementsBefore = assetsPage.getTotalAssertsNumber();//get text from counter
assetsPage.createAsset(); // add one object on the page
var numberOfElementsAfter = assetsPage.getTotalAssertsNumber(); // get text from counter after creation of object
numberOfElementsBefore.then(function(startNumberText) {
console.log("Number of Asserts before test is " + startNumberText);
return startNumberText;
});
numberOfElementsAfter.then(function(endNumberText) {
console.log("Number of Asserts after test is " + endNumberText);
expect(assertsBefore).toBe((endNumberText-1).toString());
});
});
In this test I'm verifying, that quantity of elements before text will equals quantity of elements after test minus 1.
If I not use .toString(), test result will be: Expect '21' to be 21. So, converting to string works. Maybe someone have a better solution :)

Angular filtered result not updating in the UI

I have this filter for moment:
function momentFilter() {
return function(input, format) {
if (!input) return null;
if (format == 'fromNow') {
var fn = moment(input).fromNow();
console.log('fromNow called with: ' + input + ' giving result: ' + fn);
return fn;
}
};
}
And I have this html:
<div>{{timestamp | moment: 'fromNow'}}</div>
I see the filter getting called on the digest cycle, and the relative time that gets logged in the console looks great. I.E. it goes from 1 minute ago, 2 minutes ago, etc... However the UI never updates to what the filter returns.
The first call is reflected on the UI, but after that the UI never updates.
What am I doing wrong?
EDIT:
That html is inside an ng-repeat that tracks by id. I am assuming that because nothing in the object actually changed (current time just moved), that angular never detects anything for that object.
Still not sure how to work around this.
did you try by changing object hash? normally if you change object hash code framework will understand object is modified and rendered updated object.
Filters are assumed to be idempotent, that is the same input always leads to the same output. Since timestamp doesn't change there's no reason for Angular to do anything.
If your filter is stateful and the output can change with every call then you have to mark it accordingly.
function momentFilter() {
function filter(input, format) {
if (!input) return null;
if (format == 'fromNow') {
var fn = moment(input).fromNow();
console.log('fromNow called with: ' + input + ' giving result: ' + fn);
return fn;
}
};
filter.$stateful = true;
return filter;
}

AngularJS : Why the data is not displayed in view may I use $scope.apply?

I am getting data from service and display on view using ng-repeat .Actually I am getting event when user scroll to bottom mean when user reached to bottom I will do something.When It reached to bottom I am changing the contend of my array .I am getting the correct contend in ng-repeat array (display array) but it is not reflect on view why ? May I use $scope.apply() or $scope.digest()
Here is my code
http://plnkr.co/edit/XgOxJnPXZk4DneJonlKV?p=preview
Here I am changing the contend of my display array which is not reflect on view
if (container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight) {
if(scope.var<scope.arrays.length)
scope.display=[];
var nextvar =++counter;
var increment=counter+1
console.log("nextvar:"+nextvar+"increment:"+increment)
scope.display=scope.arrays[nextvar].concat(scope.arrays[increment]);
console.log(scope.display)
}
As #Claies mentioned you should use apply(). Though the digest() would probably have worked as well.apply() calls digest() internally. He also mentioned that your variable that seems to be storing the page number gets reset to 0 each time you scroll. You should store that in a scope variable outside that handler.
I tried to fix with minimum change
http://plnkr.co/edit/izV3Dd7raviCt4j7C8wu?p=preview
.directive("scrollable", function() {
return function(scope, element, attrs) {
var container = angular.element(element);
container.bind("scroll", function(evt) {
console.log('scroll called'+container[0].scrollTop);
var counter = scope.page;
if (container[0].scrollTop <= 0) {
if (scope.var > 0)
scope.display = scope.arrays[--scope.var].concat(scope.arrays[scope.var+1]);
}
if (container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight) {
if (scope.var < scope.arrays.length)
scope.display = [];
var nextvar = ++counter;
var increment = counter + 1
console.log("nextvar:" + nextvar + "increment:" + increment)
scope.display = scope.arrays[nextvar].concat(scope.arrays[increment]);
console.log(scope.display)
scope.page = counter;
}
scope.$apply();
});
};
})
generally I would have implemented this differently. For example by having a spinning wheel on the bottom of the list that when displayed you get the rest of data.
It is difficult to give you a full working plunker. Probably you should have multiple JSON files in the plunker, each containing the data for one page so that we can add the data to the bottom of the display list.
After you modify display array you just have to call scope.$apply() so that it runs the $digest cycle and updates the view. Also you need the initialize scope.var either in your controller or the directive and modify it conditionally.
I dont if this is what you want. I have modified the plunker take a look.
http://plnkr.co/edit/J89VDMQGIXvFnK86JUxx?p=preview

AngularJS e2e Testing with Protractor

I am attemping to get the last row of a ng-repeat(ed) table via protractor to test and ensure the object I just created in a previous test run shows up. I have gotten as far as getting all of the text of the row but cannot seem to figure out through trial and error how to get each column of the last row as part of the array so I can verify each piece and then in the last column I have buttons to click which will be the next step.
The code I have so far is:
var elems = element.all(by.repeater('alert in alerts'));
elems.last().then(function(elm) {
console.log(expect(elm.getText()).toMatch('/testRunner/'));
});
As mentioned above, the expected output/output I want to check against is 'textRunner' and instead I get the entire row of text as such:
testRunner testing the runner 5 minutes No View Edit Enable
EDIT
Here is my final code:
var rows = element.all(by.repeater('alert in alerts'));
rows.last().then(function(row) {
var rowElems = row.findElements(by.tagName('td'));
rowElems.then(function(cols){
expect(cols[0].getText()).toContain('testRunner');
expect(cols[1].getText()).toContain('testing the runner');
expect(cols[4].getText()).toContain('5 minutes');
});
});
var rows = element.all(by.repeater('alert in alerts'));
rows.last().then(function(row) {
var rowElems = row.findElements(by.tagName('td'));
rowElems.then(function(cols){
expect(cols[0].getText()).toContain('testRunner');
expect(cols[1].getText()).toContain('testing the runner');
expect(cols[4].getText()).toContain('5 minutes');
});
});
Assuming your are using a <table>, and your different values are in <td>, you could check values by using the by.tagName locator strategy.
var rows = element.all(by.repeater('alert in alerts'));
var row = rows.last();
var rowElems = row. findElements(by.tagName('td'));
expect(rowElems.get(0).getText()).toMatch('/testRunner/');
expect(rowElems.get(1).getText()).toMatch('/testing/');
expect(rowElems.get(2).getText()).toMatch('/the/');
expect(rowElems.get(3).getText()).toMatch('/runner/');
expect(rowElems.get(4).getText()).toMatch('/5/');
expect(rowElems.get(5).getText()).toMatch('/minutes/');
expect(rowElems.get(6).getText()).toMatch('/No/');
expect(rowElems.get(7).getText()).toMatch('/View/');
expect(rowElems.get(8).getText()).toMatch('/Edit/');
expect(rowElems.get(9).getText()).toMatch('/Enable/');

Resources