51

I'm new on protractor, and I'm trying to implement an e2e test. I don't know if this is the right way to do this, but... The page that I want to test is not a full angular page based, so... I'm having some trouble.

On my first spec I have:

describe('should open contact page', function() {
var ptor = protractor.getInstance();

beforeEach(function(){

   var Login = require('./util/Login');
   new Login(ptor);
});

I have created this Login class, but after login I want to open the contact page, but protractor immediately try to find element before the page is fully loaded.

I've tried to use:

browser.driver.wait(function() {

    expect(browser.findElement(by.xpath("//a[@href='#/contacts']")).isDisplayed());
    ptor.findElement(by.xpath("//a[@href='#/contacts']")).click();

});

But it doesn't work... it always try to find the element before the page loads. I tried this one too:

browser.driver.wait(function() {
    expect(ptor.isElementPresent(by.xpath("//a[@href='#/contacts']")));          
    ptor.findElement(by.xpath("//a[@href='#/contacts']")).click();
});

I'm able to do that using browser.sleep(); but I don't think that is a good option. Any idea? On my login class I have:

ptor.ignoreSynchronization = true;

How can I wait for this @href='#/contacts before protractor tries to click on it?

Mohsin Awan
  • 1,176
  • 2
  • 12
  • 29
Muratso
  • 961
  • 1
  • 7
  • 10
  • I guess your should run the tests after the page is loaded. You can set that in your test runner, config file, etc... I think. Why don't you use karma for client side testing? – inf3rno Feb 27 '14 at 15:39
  • http://stackoverflow.com/questions/17070522/can-protractor-and-karma-be-used-together Hmm karma not recommended with protractor... – inf3rno Feb 27 '14 at 15:40
  • The easiest workaround to put your whole `describe` into a callback for the `ready` event, but I don't know this system and the test runner your are using, so maybe it won't work... – inf3rno Feb 27 '14 at 15:42
  • I'm using jasmine, on my config I'm using defaultTimeoutInterval: 50000 and allScriptsTimeout: 50000. – Muratso Feb 27 '14 at 16:47
  • Using async tests does not work? http://www.htmlgoodies.com/beyond/javascript/test-asynchronous-methods-using-the-jasmine-runs-and-waitfor-methods.html#fbid=8R0ps_w945k – inf3rno Feb 27 '14 at 16:57
  • I am not sure, so you think the problem is that the test runs before the ready event? I don't think that is possible. – inf3rno Feb 27 '14 at 16:58
  • Ohh I see now, so the problem is with the protractor. – inf3rno Feb 27 '14 at 17:00
  • Am I sure, that this is a similar problem? http://stackoverflow.com/questions/12187669/how-do-i-reliably-execute-jasmine-tests-that-utilize-requirejs-via-phantomjs – inf3rno Feb 27 '14 at 17:02
  • I don't think so. I'm using chrome driver and... my test run just like they should... but every time that my page reloads, I have to put a driver.sleep(); otherwise my findElement is not be able to find the specified element. I just wanna know if there is any other option for this sleep... something more... dynamic. – Muratso Feb 27 '14 at 17:14
  • Strange, does it need only a tick or a longer time? Are you your that these tests should not be async? – inf3rno Feb 27 '14 at 17:17
  • I found that there is a protractor/jasminewd driver to ease async testing, but I am confused about your problem :D – inf3rno Feb 27 '14 at 17:17
  • I should try to use this protractor before any other comment. Can you send me a small example code which does not work? – inf3rno Feb 27 '14 at 17:18
  • Ok, here's the problem... as I said before, the page is not a full angular page based. On my beforeEach I'm logging in the system. After the login, on my page I have a black loading screen, and then.. after all the page is loaded, the black screen disappear and the page appears. The problem is, after the beforeEach function is executed, protactor immediately execute my first "it", but at this point of the test, the page isn't fully loaded, so it can't find the element. My homepage is a login page, so i MUST login before do anything. – Muratso Feb 27 '14 at 17:25
  • So... how can i "wait" between login and the page load without using browser.sleep()? – Muratso Feb 27 '14 at 17:26
  • I'll paste here what i'm trying to do. Just a sec.. – Muratso Feb 27 '14 at 17:34
  • Did you try to force the promise to be solved using: `browser.findElement(by.xpath("//a[@href='#/contacts']")).isDisplayed().then(function(){// do click() and other actions};` ? – glepretre Feb 27 '14 at 17:35
  • Try to put `ptor = protractor.getInstance();` under the `beforeEach` or `it`. I checked about 5-10 code, but nobody sets its value in the describe. Ofc. this is just a guess... I am still learning how to use that test environment... http://blog.busymachines.com/frontend/angularjs/testing/2013/10/28/testing-with-jasmine-and-protractor.html – inf3rno Feb 27 '14 at 17:39
  • @glepretre According to this: http://engineering.wingify.com/posts/e2e-testing-with-webdriverjs-jasmine/ Protractor automatically solves the promises. – inf3rno Feb 27 '14 at 17:56
  • @user5968: Use the https://github.com/angular/protractor/blob/master/jasminewd/index.js jasmine wd driver. It automatically wraps jasmine functions for async testing. After that you can add a 3rd timeout parameter for each tests `it(title, callback, msec)` function, so you don't have to write the timeout manually... – inf3rno Feb 27 '14 at 17:59
  • But.. I guess when ptor.ignoreSynchronization = true; it don't solve the promises by itself. – Muratso Feb 27 '14 at 18:00
  • Nah that's a good question :-) I know this system only for 1 hour... – inf3rno Feb 27 '14 at 18:01
  • I guess this tutorial will be helpful too: http://engineering.wingify.com/posts/e2e-testing-with-webdriverjs-jasmine/ – inf3rno Feb 27 '14 at 18:03
  • Btw. if you are at the beginning of your e2e tests, I'd rather use [karma with phantomjs](http://scriptogr.am/pploug/post/karma-unit-tests-in-phantomjs). It is easy to setup karma, and has great support. But that's my opinion... :-) – inf3rno Feb 27 '14 at 18:16
  • Does karma works with angular? And it has support for sauce labs? – Muratso Feb 27 '14 at 18:18
  • Yes... it works, i'll take a look on it. But, if you have any idea on how to solve this problem on protector, I'll be very grateful. – Muratso Feb 27 '14 at 18:30
  • I am sure it works with angular, I saw about 50 times that... – inf3rno Feb 27 '14 at 18:30
  • It is out of my league. I mean I don't think I'll ever write e2e tests or angular projects... I currently use backbone and I don't test dom elements, just the models... Writing automated tests for UI is wasting resources I think, but that's my opinion... – inf3rno Feb 27 '14 at 18:34
  • 1
    On my case that's not a waste of resource. We kind of sell "things" to the government, and before we can sell, we have to do a kind of "concept test", that is a test to know if the system it's on accord with their expectation. So... this kind of test that I have to do almost everyday, would be very useful if I could automate them. – Muratso Feb 27 '14 at 18:39
  • Ye I understand. I'm sorry I cannot help more... Maybe somebody else... – inf3rno Feb 27 '14 at 19:23
  • Thanks anyway, I hope I'll find out. – Muratso Feb 27 '14 at 19:25
  • I have a video for you about application architecture: https://www.youtube.com/watch?v=WpkDN78P884 It has a part about why testing View is not so important, but anyways the whole presentation is interesting... Enjoy! – inf3rno Feb 28 '14 at 00:47
  • Somebody recommended me the [nightwatch.js](http://nightwatchjs.org/) in this problem domain. (I hope I used the right words, sometimes I am unsure of my English.) I think you should check that framework, it is much cleaner, than protractor. – inf3rno Mar 09 '14 at 07:23
  • `protractor.getInstance();` is not a function – chovy Mar 31 '16 at 23:15

9 Answers9

52

Protractor 1.7.0 has also introduced a new feature: Expected Conditions.

There are several predefined conditions to explicitly wait for. In case you want to wait for an element to become present:

var EC = protractor.ExpectedConditions;

var e = element(by.id('xyz'));
browser.wait(EC.presenceOf(e), 10000);

expect(e.isPresent()).toBeTruthy();

See also:

Community
  • 1
  • 1
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
34

I finally find out...

   var waitLoading = by.css('#loading.loader-state-hidden');

   browser.wait(function() {
       return ptor.isElementPresent(waitLoading);
   }, 8000);

   expect(ptor.isElementPresent(waitLoading)).toBeTruthy();

   var openContact = by.xpath("//a[@href='#/contacts']");
   element(openContact).click();

With this protractor could wait for that element until it loading page disappears. Thanks for those who tried to help XD.

Muratso
  • 961
  • 1
  • 7
  • 10
  • 2
    I was looking to use this to test a custom angular flash message content. But protractor seems unable to get the corresponding DOM element. I resolved the issue by using selenium driver instead of protractor (e.g. use `browser.driver` instead of `ptor` in the above code). This is not a direct answer to the question but I though it might be helpful. – Raphaël Sep 12 '14 at 10:16
25

I had the same problem you were having for the longest time while using protractor. In my e2e test I start in a non angular app, then get into an angular portion, then get back out to a non angular portion. Made things tricky. The key is to understand promises and how they work. Here's some examples of my real world code in a functioning e2e test. Hoping this gives you an idea of how to structure your tests. Probably some bad practice in this code, please feel free to improve upon this, but I know that it works, maybe not the best way.

To get to angular I use

var ptor;
var events = require('events');
var eventEmitter = new events.EventEmitter();
var secondClick = require('./second-click');

beforeEach(function () {
    browser.driver.get('http://localhost:8080/');
},10000);

it("should start the test", function () {
    describe("starting", function () {
        it("should find the  link and start the test", function(){
            var elementToFind = by.linkText('Start'); //what element we are looking for
            browser.driver.isElementPresent(elementToFind).then(function(isPresent){
                expect(isPresent).toBe(true); //the test, kind of redundant but it helps pass or fail
                browser.driver.findElement(elementToFind).then(function(start){
                    start.click().then(function(){ //once we've found the element and its on the page click it!! :) 
                        ptor = protractor.getInstance(); //pass down protractor and the events to other files so we can emit events
                        secondClick(eventEmitter, ptor); //this is your callback to keep going on to other actions or test in another file
                    });
                });
            });
        });
    });
},60000);

While in angular this code works

 describe("type in a message ", function(){
        it("should find and type in a random message", function(){
            var elementToFind = by.css('form textarea.limited');
            browser.driver.isElementPresent(elementToFind).then(function(isPresent){
                element(elementToFind).sendKeys(randomSentence).then(function(){
                    console.log("typed in random message");
                    continueOn();
                });
            });
        });
    },15000);

After exiting angular

browser.driver.wait(function(){
   console.log("polling for a firstName to appear");
   return    browser.driver.isElementPresent(by.name('firstName')).then(function(el){
         return el === true;
       });
     }).
   then(function(){
       somefunctionToExecute()
    });

Hope that gives some guidance and helps you out!

asherrard
  • 683
  • 1
  • 8
  • 11
  • 3
    I guess you still do not know promises very well. If you return from within first .then, you can chain other .then calls so that you get a flat structure. – Ali Motevallian Aug 10 '15 at 04:57
  • 3
    I wish someone would have told me that a year ago when I wrote this ha! Thank you @AliMotevallian I haven't written anything with the newer version of protractor, I'd like to revisit it and clean up the code for the new versions and hopefully make it a lot simpler. – asherrard Aug 10 '15 at 13:18
9
browser.driver.wait(function() {
    return browser.driver.isElementPresent(by.xpath("//a[@href='#/contacts']"));
});

This works for me too (without the timeout param)..

for more information, see http://angular.github.io/protractor/#/api?view=webdriver.WebDriver.prototype.wait

Henry Neo
  • 2,357
  • 1
  • 24
  • 27
1

Thanks to answers above, this was my simplified and updated usage

function waitFor (selector) {
  return browser.wait(function () {
    return browser.isElementPresent(by.css(selector));
  }, 50000);
}
Kirk Strobeck
  • 17,984
  • 20
  • 75
  • 114
0

Have you tried putting the ng-app in the <html> tag (assuming this part of code is under your control)? This solved a lot of initialization timing problems for me.

Konstantin A. Magg
  • 1,106
  • 9
  • 19
0

Best way to use wait conditions in protractor that helps to show proper error message to particular element if test case failed

const EC = ExpectedConditions;
const ele = element(by.xpath(your xpath));

return browser.wait(EC.visibilityOf(ele),9000,'element not found').then(() => {
            ele.click();
         });
0

I'm surprised that nobody has added this solution. Basically, if you are using modal dialogues you often get an element visible and available to click but not being clickable due to the modal dialogue being in front of it. This happens because protractor moves faster than angular and is ready to click the next element while angular is still closing the modal.

I suggest using

public async clickElementBug(elementLocator: Locator) {
const elem = await element(elementLocator);
await browser.wait(
  async function() {
    try {
      await elem.click();
      return true;
    } catch (error) {
      return false;
    }
  },
  this.TIMEOUT_MILLIS,
  'Clicking of element failed: ' + elem
);

}

Ray
  • 1,134
  • 10
  • 27
0

browser.wait may sound too ordinary, but it's not!

browser.wait is the way to go. Just pass a function to it that would have a condition which to wait for. For example wait until there is no loading animation on the page

let $animation = $$('.loading');

await browser.wait(
  async () => (await animation.count()) === 0, // function; if returns true it stops waiting; can wait for anything in the world if you get creative with it
  5000, // timeout
  `message on timeout` // comment on error
);

Make sure to use await

You can also use existing library called ExpectedConditions that has lots of predefined conditions to wait for

You can't imagine what you can do with it...

A few of my favorite ones:

wait until the number of browser's tab's is 2

// wait until the number of browser's tab's is 2
await browser.wait(
  async () => {
    let tabCount = await browser.getAllWindowHandles();
    return tabCount.length === 2;
  },
  5000,
  'the url didnt open in a new window'
);

wait until the loading animation is gone for at last 750ms

// wait until the loading animation is gone for at last 750ms
await browser.wait(
  async () => (await this.$$loadAnimations.count()) === 0 && !(await browser.sleep(750)) && (await this.$$loadAnimations.count()) === 0,
  5000,
  `waiting timeout`
);

wait for ANY number of elements to be present

// wait for any number of elements to be present
async waitForElements($elem, timeout = 120000, start = +new Date()) {
    let conditions = [];

    for (let i = 0; i < $elem.length; i++) {
        conditions.push(ExpectedConditions.presenceOf($elem[i]));
    }

    await browser.wait(
        ExpectedConditions.and(...conditions), 
        remainingTimeout(timeout, start), 
        `wait for all elements`
    );
}

// and use

await waitForElements([
  $usernameField, 
  $passwordFiend, 
  $submitButton
])
Sergey Pleshakov
  • 7,964
  • 2
  • 17
  • 40