Protractor tests inconsistently passing / failing

2019-03-09 03:01发布

问题:

My Protractor e2e tests are inconsistently passing and failing.

It seems this could be due to asynchronous javascript, as discussed here: Protractor : How to wait for page complete after click a button?.

However, here it's mentioned that Protractor tests automatically execute sequentially / synchronously: https://github.com/angular/protractor/issues/909

My test script:

describe('Login', function() {

  var ptor;

  beforeEach(function() {
    browser.get('https://127.0.0.1:8443');
    ptor = protractor.getInstance();
    element(by.id('splash')).click();
    browser.ignoreSynchronization = true;  // <-- to proceed beyond splash screen
  });

  describe('with correct email and password', function() {

    beforeEach(function() {
        element(by.id('email')).sendKeys('admin@email.com');
        element(by.id('password')).sendKeys('adminpassword');
        element(by.id('loginButton')).click();
    });

    afterEach(function() {
        ptor.findElement(by.id('logout')).then(function(elem) {
            elem.click();
        });
    });

    it('does not show alert', function() {  // <-- sometimes passes, sometimes fails
        expect(browser.isElementPresent(by.css('.alert-danger'))).toBe(false);
    });

    it('changes route to /admin', function() {  // <-- sometimes passes, sometimes fails
        expect(browser.getCurrentUrl()).toMatch(/\/admin/);
    });
  });
});

In the two tests above, either both tests will pass, or one/both of the tests will fail with these messages:

Failures:

1) Login with correct email and password does not show alert
Message:
  NoSuchElementError: no such element
...
==== async task ====
WebDriver.findElement(By.id("logout"))
...

or

Failures:

1) Login with correct email and password changes route to /admin
Message:
  NoSuchElementError: no such element
...
==== async task ====
WebDriver.findElement(By.id("logout"))
...

Thoughts / help much appreciated.

回答1:

I was able to resolve the issue based on the following:

  • Avishay's answer here about adding ptor.waitForAngular():
    No element found using locator: by.model() error

  • Changing browser.get to ptor.get, as in Harri Siirak's answer here:
    Protractor times out waiting for sync with page when using $resource

  • juliemr's comment here about ignoreSynchronization being an instance variable, and changing browser.ignoreSynchronization=true to ptor.ignoreSynchronization=true:
    https://github.com/angular/protractor/issues/49

  • glepretre's answer here about using .then():
    Protractor : How to wait for page complete after click a button?

As mentioned by Nguyen Vu Hoang's comment to the original question, I am testing a pure Angular app with what I think is pure Protractor (no webdriver calls). I know ptor.ignoreSynchronization=true should not be required in this case, but for some reason, the tests are not proceeding at button click without this setting.

My new spec:

describe('Login', function() {

  var ptor;

  beforeEach(function() {
    ptor = protractor.getInstance();
    ptor.ignoreSynchronization = true;
    ptor.waitForAngular();
    ptor.get('https://127.0.0.1:8443');
    ptor.findElement(by.id('splash')).then(function(elem) {
        elem.click();
    });
  });

  describe('with correct email and password', function() {

    beforeEach(function() {
        ptor.findElement(by.id('email')).then(function(elem) {
            elem.sendKeys('admin@email.com');
        });

        ptor.findElement(by.id('password')).then(function(elem) {
            elem.sendKeys('adminpassword');
        });

        ptor.findElement(by.id('loginButton')).then(function(elem) {
            elem.click();
        });
    });

    afterEach(function() {
        ptor.findElement(by.id('logout')).then(function(elem) {
            elem.click();
        });
    });

    it('does not show alert', function() {
        expect(ptor.isElementPresent(by.css('.alert-danger'))).toBe(false);
    });

    it('changes route to /admin', function() {
        expect(ptor.getCurrentUrl()).toMatch(/\/admin/);
    });
  });
});


回答2:

There is also an another technique to make your tests more stable: Explicit Waits and Expected Conditions (docs).

I've found using Expected Conditions especially useful when testing against non-angular pages or angular applications that have a lot of animations involved.

For example, you can wait for an element to be clickable before making a click:

var EC = protractor.ExpectedConditions;
var link = element(by.id("mylink"));

browser.wait(EC.elementToBeClickable(link), "10000", "The link is still not clickable");
link.click();

There are also other built-in Expected Conditions, such as:

  • presenseOf()
  • visibilityOf()
  • alertIsPresent()
  • textToBePresentInElementValue()
  • etc

And, it is easy to write a custom Expected Condition, example use case:

  • Testing link style changes

You can also combine Expected Conditions using and, or and not, e.g.:

var urlChanged = function() {
  return browser.getCurrentUrl().then(function(url) {
    return url != 'http://www.angularjs.org';
  });
};

// condition to wait for url to change, title to contain 'foo', and $('abc') element to contain text 'bar'
var condition = EC.and(urlChanged, EC.titleContains('foo'), EC.textToBePresentInElement($('abc'), 'bar'));
$('navButton').click();
browser.wait(condition, 5000); //wait for condition to be true.


回答3:

browser.ignoreSynchronization = true; has a global effect for all your tests. you may have to set it back to false, so protractor waits for angular to be finished rendering the page. e.g. in or before your second beforeEach function