8
votes

I'm having trouble getting protractor to behave as expected when testing my angular app. My spec file looks like this:

describe('app login', function() {
   it('should allow admin user to log in', function() {
       browser.get('http://localhost:3008');

    //we can find the log in link
    expect(element(by.id('login-link')).getText()).toContain('Log in');

    //open login dialog
    element(by.id('login-link')).click();
    browser.ignoreSynchronization = true;
    browser.sleep(1000);

    //enter credentials
    element(by.id('login-username')).sendKeys('User1');
    element(by.id('login-password')).sendKeys('Password1');
    browser.sleep(1000);

    //log in
    var el = element(by.id('login-btn'));
    //WORKS IF BELOW LINE IS COMMENTED OUT 
    el.click();
    browser.sleep(1000);

    //display confirms login
    expect(element(by.id('user-display')).getText()).toContain('User1');

  });
});

Note that I was getting synchronization errors at the start which is why I have the ignoreSynchronization flag set to true and all those browser.sleeps.

Now here's the thing: The test will go through fine if I remove the el.click() statement (and the final expect call). However, once that line is included, I get NoSuchElementError: No element found using locator: By.id("login-username"). Notice that this element is not the one I am actually trying to click, which is part of the bizarreness.

4
Can you show your html? - Nagarjuna Reddy
Why do you need to set ignoreSynchronization to true? How do you bootstrap angular and where ng-app is defined? Thanks - alecxe
@alecxe: I haven't worked out why I need the ignoreSynchronization! It never syncs up otherwise. I understand that can relate to http calls (there aren't any) or $timeout. I haven't been able to track down any of those either, but I'm less sure as I'm not aware of any way to find that out other than inspecting the codebase. ng-app is defined on the html element. - see sharper

4 Answers

2
votes

let me start by breaking down your question in 3 parts. Let's take it a step at a time:
1)ignoreSynchronization flag set to true: It instructs protractor to not wait for Angular promises. This is used when you are i)testing scenarios around waiting for $http or $timeout to resolve ii)testing against a non-angular site. These are the times when you should set ignoreSynchronization setting to true. Ask yourself is it needed for your test pre-requisite? Going by your explanation above, I believe you can set it to false and continue your testing safely. By setting this to true when it is not needed you are instructing protractor to not attempt to synchronize with the page before performing actions. This can be harmful because Protractor will not wait until $timeouts and $http calls have been processed, which can cause tests to become flaky. more on: https://github.com/angular/protractor/blob/9891d430aff477c5feb80ae01b48356866820132/lib/protractor.js#L158


You can do away with unnecessary browser.sleep() once you set this flag to False.

2)Protractor not finding element: There are various reasons for which an element is not detected by Protractor:
-Element does not fit it the display window.
-Element is not visible, but present.
-Asynchronous task not handled properly.
-Another element is overlayed on the other element

I suggest you try out:

  1. Handling all your asynchronous event using either promises, async/await or callbacks.
describe('app login', function() {
  it('should allow admin user to log in', async() => {
    await browser.get('http://localhost:3008');

    //we can find the log in link
    expect(await element(by.id('login-link')).getText()).toContain('Log in');

    //open login dialog
    await element(by.id('login-link')).click();
    //try putting this setting in your protractor.conf file instead of describe 
    //block
    //browser.ignoreSynchronization = false;
    
    //enter credentials
    await element(by.id('login-username')).sendKeys('User1');
    await element(by.id('login-password')).sendKeys('Password1');

    //log in
    var el = element(by.id('login-btn'));
    //WORKS IF BELOW LINE IS COMMENTED OUT 
    await el.click();

    //display confirms login
    expect(await element(by.id('user-display')).getText()).toContain('User1');

    });
});

More info on how to change your code: https://jasmine.github.io/tutorials/async



3)Excessive use of browser.sleep(1000);: In your code, using sleep() renders it fragile and will sometimes fail if the system runs slower than normal. It is an invitation to making your test fail flaky, often in non-reproducible scenarios. So unless you want to spend your time in futile debugging, stop using it.

Instead use these wait strategies: explicit waits, waitforAngular; to make sure your asynchronous events are completed before going to the next step. Incase you need to know more on this, leave a comment below, otherwise I am keeping this answer strictly to the question you have asked. Ask away!

0
votes

You have to wait until your element appear completely by using:

browser.driver.wait(function() {
            return element(by.css(YourElement'/login-username)).isDisplayed().then(function(IsVisible) {
                return IsVisible;
            });
        }, 10000);

Or

browser.driver.wait(function() {
            return element(by.css(YourElement'/login-username)).isPresent().then(function(IsVisible) {
                return IsVisible;
            });
        }, 10000);

Or you want to verify the used selector if its correct or not by using GoogleChrome console by typing: document.querySelector('Your CSS Selector').

0
votes

You can try the following:

    let EC: any = protractor.ExpectedConditions;
    return browser.wait(EC.visibilityOf($('#login-username')), 6000);

I ran into the same problems while testing my login page, where protractor was searching for elements before they were rendered. Using the protractor's expected conditions tells the framework to wait for a particular condition to happen (with a time-out limit, of course) of an element before proceeding.

0
votes

In case your error might be linked to the display size of your testing window, you may want to maximize your driver's visibility upon starting the test suit.

To do this, you can include the following lines of code into your protractor.conf.js file:

onPrepare() {
    browser.driver.manage().window().maximize();
}