I am working on trying to get some end to end tests implemented for an AngularJS site, and I am having a bit of an issue getting past a call to the IsPresent() method on the ElementFinder returned by a call to the element method.
Sadly I am governed by rules from my employer preventing me from posting ANY of our code on StackOverflow, but essentially this is what I am doing...
describe('Some feature', function () {
it('Some Scenario', function () {
browser.get(browser.baseUrl + '/#/somePage');
var ee = element(by.css('.test-comment'));
expect(ee.isPresent()).toBeTruthy();
});
});
If I comment out the call to the expect() method, then the test executes and passes without issue. If I leave it in, I get :
Failed: Timed out waiting for Protractor to synchronize with the page
after 20 seconds. Please see
https://github.com/angular/protractor/blob/master/docs/fa q.md. The
following tasks were pending:
- $timeout: function (){n=null}
This doesn't make any sense to me - the IsPresent() method returns a promise, which is resolved by the expect method, or at least that is what I would expect to happen.
Any clues?
Try the browser.isElementPresent() instead:
expect(browser.isElementPresent(ee)).toBeTruthy();
If you are curious about the differences, please see:
In protractor, browser.isElementPresent vs element.isPresent vs element.isElementPresent
Also, you may introduce the "presence of" explicit wait before making the expectation:
var EC = protractor.ExpectedConditions;
var ee = element(by.css('.test-comment'));
browser.wait(EC.presenceOf(ee), 5000);
expect(ee.isPresent()).toBeTruthy();
.isDisplayed() might be of help to you as well.
See What is the difference between the isPresent and isDisplayed methods for more info.
Related
So in my factory I have a loop which requests HTTP calls and adds them to a promise array.
I then do a $q.all on the result to build a model.
When I come to test this however I can't get HTTP to make all the calls, it only makes the last one, I need it to make all the calls and build the model.
Below is very cut down code, ( I use 7 dates, but wanted to keep things short)
Factory Code
function getLatestData(){
var dateArray= ['2017-09-21','2017-09-22']
for (i = 0; i < 2; i++) {
var url = 'data-server/date/[i]'
promises.push(getData(url)); // getData is a simple $http function call.
}
return $q.all(promises).then(function(response){
buildModel(reponse);
});
}
So when I come to test this, I've got something like (I did try a loop but that failed).
httpBackend.expectGET('data-server/date/2017-09-21' ).respond(mockData[0]);
httpBackend.expectGET('data-server/date/2017-09-22' ).respond(mockData[1]);
rootScope.$apply();
modelFactory.getLatestData().then(function(response){
expect(response).toEqual(mockModelData);
})
So I console.log the get URL and I see all the URL requests are the same, they don't seem to be updating which results in this error
Error: Unexpected request: GET 'data-server/date/2017-09-22'
Expected GET 'data-server/date/2017-09-21'
because it's always the last httpBackend.expectGET that's taken.
What am I missing?
My problem was mocking.
I left this out of my example because I thought it wasn't relevant and added complication, but to build the dates I used the momentJS Library.
so
var url = 'data-server/date/[i]'
is
var url = 'data-server/date/'+factory.getMoment().add(i,'d').format('YYYY-MM-DD');
The factory.getMoment is just a wrapper for moment, the idea being I could over ride this in the unit tests to provide me with a 'given' date object.
funciton getMoment(){
return moment();
}
Anyway in my tests, I had this
var mockDate = moment('2017-09-21');
spyOn(factory, 'getMoment').and.returnValue(moment('2017-09-21'));
httpBackend.expectGET('data-server/date/'+mockDate.format('YYYY-MM-DD').respond(mockData[0]);
httpBackend.expectGET('data-server/date/'+mockDate.add(1,'d').format('YYYY-MM-DD').respond(mockData[1]);
Thinking that each time this would be called, it would give me back this mock, guess I was wrong about that.
What I needed to do, as pointed out by a colleague, was to use jasmine's clock mock.
beforeEach(function () {
jasmine.clock().install();
})
afterEach(function () {
jasmine.clock().uninstall();
})
Then in my test I set up the date time with
mockDate = moment('2017-09-21'); // always use moment as JS date if badly broken and just can't be trusted!
jasmine.clock().mockDate(mockDate.toDate());
httpBackend.expectGET('data-server/date/'+moment().add(0, 'days').format("YYYY-MM-DD").respond(mockData[0]);;
httpBackend.expectGET('data-server/date/'+moment().add(1, 'days').format("YYYY-MM-DD").respond(mockData[1]);;
(the above is in a loop)
I've removed my spy and now I have the dates and requests working as I expected.
Hope this helps someone else who finds themselves scratching their head for days trying to figure out why their tests are not working!
I'm using Protractor with Cucumber to write some tests but I'm stuck at some point. In step after login, I'm rerouting to another page using browser.get(url) function provided by protractor. But it always returns before the page is completely loaded. I have tried many solutions so far but no luck. I have tried with browser.wait, browser.get(url).then(function(){ // code when it loads}) but Im getting 0 positive results.
Here's my code:
// Steps will be implemented here
this.Given(/^I am logged in as user \'([^\']*)\'$/, function (user, callback) {
console.log('USER: ' + user);
browser.driver.get('https://cit-was70-l06/ipa')
browser.driver.findElement(by.xpath('my_xpath')).sendKeys(user);
browser.driver.findElement(by.xpath('my_xpath')).sendKeys(user);
browser.driver.findElement(by.xpath('my_xpath')).click().then(callback);
});
this.Then(/^The screen is shown, with title \'([^\']*)\'$/, function (title, callback) {
console.log('Title from feature file: ' + title);
var url = 'some/url/in/application/';
browser.get(url).then(function(){
// This portion executes before page is completely loaded.
// So if I try to get any element, it throws me an error.
// [15:32:13] E/launcher - "process.on('uncaughtException'" error, see
// launcher
// [15:32:13] E/launcher - Process exited with error code 199
// It works if I add static delay to wait for page load completely
// but that will not be a good solution if I have too much pages to test
callback();
});
console.log('After page laoad');
});
Any suggested work around will be much appreciated.
[15:32:13] E/launcher - "process.on('uncaughtException'" error, see launcher
[15:32:13] E/launcher - Process exited with error code 199
The above error can be caused due to various reasons mostly related to promises. But it should throw the correct message. There is already a work around provided here https://github.com/angular/protractor/issues/3384 to catch the exact error message.
You could change the launcher.ts file in your protractor dependency as mentioned in above forum to catch the error inorder to debug your issue.
And I would suggest you to return your promises instead of callbacks when writing step definitions in protractor-cucumber, in this way cucumber would know when to complete its async actions.
Try the below code.check whether it helps.
browser.get(url);
browser.waitForAngular();
then try to call your function.
Use protractor.ExpectedConditions to check visibility of any elements on page which will be displayed after successful login. Write a customized method as shown below.
If element displayed, then navigate other page using browser.get();
Code Snippet
EC = protractor.ExpectedConditions;
//targetElement=element(locator);
this.isElementVisible = function (targetElement, timeOut) {
'use strict';
timeOut = timeOut !== undefined ? timeOut : 8000;
browser.wait(EC.visibilityOf(targetElement),
timeOut).thenCatch(function()
{
assert.fail(' element is not visible');
});
};
I just started working with cucumberJs, gulp and protractor for an angular app and noticed, luckily as all my steps were passing, that if you don't pass in and use that 'callback' parameter in the step definition, cucumberJs might NOT know when this step is completed and will skip other steps and mark them all as 'passed'
Below is an example from the cucumberJs doc: https://github.com/cucumber/cucumber-js
Example 1:
this.Given(/^I am on the Cucumber.js GitHub repository$/, function (callback) {
// Express the regexp above with the code you wish you had.
// `this` is set to a World instance.
// i.e. you may use this.browser to execute the step:
this.visit('https://github.com/cucumber/cucumber-js', callback);
//
The callback is passed to visit() so that when the job's finished, the
next step can
// be executed by Cucumber
.
});
Example 2:
this.When(/^I go to the README file$/, function (callback) {
// Express the regexp above with the code you wish you had.
Call callback() at the end
// of the step, or callback(null, 'pending') if the step is not yet implemented:
callback(null, 'pending');
});
Example 3:
this.Then(/^I should see "(.*)" as the page title$/, function (title, callback) {
// matching groups are passed as parameters to the step definition
var pageTitle = this.browser.text('title');
if (title === pageTitle) {
callback();
} else {
callback(new Error("Expected to be on page with title " + title));
}
});
};
I understand you have 2 choices here:
a. Either you return a promise and don't pass the call back OR
b. You pass in the callback parameter and call it whenever the step definition is complete so cucumberJs knows to return and go to next step or next scenario.
However, I tried both above and still running into a weird situation where the top two scenarios will work NORMALLY as you would expect, but the third and fourth scenario within that same feature file will be skipped and all passed.
Is there anything special to consider about features with more than 2 scenarios?
As long as I have <= 2 scenarios per feature files, everything works fine, but the moment I had a third scenario to that feature file, that third scenario is ignored and skipped.
Any ideas?
Without seeing your actual steps I can't say for sure but it sounds like an asynchronous issue or dare I say it, a syntax error in the scenario. Have you tried changing the order of the scenarios to see if that has an impact.
Is there a way to have an expectation that an element is eventually on the page? e.g. a way for
browser.wait(protractor.ExpectedConditions.presenceOf(element(by.partialLinkText('Continue'))), 1000, 'Unable to find continue link');
to fail with an expectation error instead of a timeout? Essentially a way to have a isEventuallyPresent() instead of isPresent() in the line below
expect(element(by.partialLinkText('Continue')).isPresent()).toBe(true);
For reference, I'm using browser.ignoreSynchronization = true even though it's an Angular app, and using Jasmine (at least for now).
Using the facts that
browser.wait returns a promise that is resolved once the condition function returns truthy, or rejected if it times out.
If expect is passed a promise, it only runs the expectation when the promise is resolved
You can make a function that wraps a call to browser.wait
function eventual(expectedCondition) {
return browser.wait(expectedCondition, 2000).then(function() {
return true;
}, function() {
return false;
});
}
and then create an expectation
expect(eventual(protractor.ExpectedConditions.presenceOf(element(by.partialLinkText('Continue'))))).toBe(true);
Or, to make it work on any browser instance, you can monkey-patch the Protractor prototype
protractor.Protractor.prototype.eventual = function(expectedCondition) {
return this.wait(expectedCondition, 2000).then(function() {
return true;
}, function() {
return false;
});
}
and can be used as
expect(browser.eventual(protractor.ExpectedConditions.presenceOf(element(by.partialLinkText('Continue'))))).toBe(true);
To avoid timeouts, you must make sure that the timeout passed to browser.wait is less than the the Jasmine async test timeout, which is specified as jasmineNodeOpts: {defaultTimeoutInterval: timeout_in_millis} in the protractor configuration file
The presenceOf expected condition used with a browser.wait() would allow to have a single line in the test:
var EC = protractor.ExpectedConditions;
browser.wait(EC.presenceOf(element(by.partialLinkText('Continue'))), 1000, 'Unable to find continue link');
where EC is protractor.ExpectedConditions - I usually make it global in onPrepare() through the global namespace.
Note that in case of a failure, you would have a Timeout Error, but with the Unable to find continue link error description.
As a side note, it is important to provide a meaningful custom error description, as you've already did. If you want to enforce it, there is a eslint-plugin-protractor plugin to ESLint static code analysis tool that would warn you if there is a browser.wait() used without an explicit error description text.
Due to Protractor 2.0's breaking changes, I have to refactor my at() function. I use this on non-angular pages to both wait for the page to load, and return true, so it's expectable (if that's a word).
Because 2.0 no longer returns a promise from a locator, it's breaking my wait (wait fires immediately). Thus, I've turned to the new ExpectedConditions... which works fine. My question is, am I using this correctly, or is there a better solution?
Page object locator:
this.pageLoaded = $('div#splash[style="display: none;"]');
Works in Protractor 1.8.0 (breaks in 2.0.0):
this.at = function() {
var that = this;
return browser.wait(function() {
return browser.isElementPresent(that.pageLoaded);
});
};
My working solution for Protractor 2.0.0:
this.at = function() {
var that = this;
return browser.wait(function() {
return protractor.ExpectedConditions.presenceOf(that.pageLoaded);
});
};
And for example, I would call this like so:
expect(mainPage.at()).toBeTruthy();
It looks like there was another breaking change in the Protractor 2.0 changelog, referring to a problem in WebDriver 2.45 that makes the wait parameter required:
Due to changes in WebDriverJS, wait without a timeout will now default to waiting for 0 ms instead of waiting indefinitely.
Before:
browser.wait(fn); // would wait indefinitely
After:
browser.wait(fn, 8000) // to fix, add an explicit timeout
This will be reverted in the next version of WebDriverJS.
In our Protractor tests, we always refer to a common helper class var helper = require("..\helpers.scenario.js"). To fix this problem, we just wrapped browser.wait in the helper class and passed in a timeout.
wait: function(f) {
return browser.wait(f, 30000);
},
We then used find-replace to change from browser.wait to helper.wait.