Catch certain webdriver errors like StaleElementReferenceError #543

Closed
elgalu opened this Issue Feb 21, 2014 · 16 comments

Comments

Projects
None yet
5 participants
@elgalu
Contributor

elgalu commented Feb 21, 2014

I'm using protractor intensively on a heavy angular app.

Sometimes i get random webdriver errors that depend on unknown factors like server speed at that time or browser processor cpu too high at one particular moment.
They happen more often in IE10/IE9, less often in FF27/IE11 and almost never in Chrome 32.

My question/request is, if there is a way i can enclose some kind of try { } catch() block around webdriver errors like StaleElementReferenceError or NoSuchWindowError so i can recover and retry instead of failing the test for things that are more selenium-webdriver related than the application or protractor itself.

I've tried this but doesn't catch the error probably because webdriver errors aren't exposed as javascript exceptions:

// Fighting against StaleElementReferenceError
var retryFindIdxElementAndExpectTextContains = function(listElms, idx, subElm, subText, attempts) {
    if (attempts == null) {
        attempts = 3;
    };

    browser.driver.findElements(listElms).
        then(function(elems) {
            elems[idx].findElement(subElm).then(function(elm) {
                try {
                    expect(elm.getText()).toContain(subText);
                } catch(err) {
                    if (attempts > 0) {
                        retryFindIdxElementAndExpectTextContains(listElms, idx, subElm, subText, attempts - 1);
                    } else {
                        throw err;
                    }
                }
            });
        });
};

@juliemr juliemr added the question label Feb 27, 2014

@juliemr

This comment has been minimized.

Show comment
Hide comment
@juliemr

juliemr Feb 27, 2014

Member

You should be able to use the promise error handling returned by elm.getText(). Try

elm.getText().then(function() { // passing case}, function(err) { // error handling here})

Member

juliemr commented Feb 27, 2014

You should be able to use the promise error handling returned by elm.getText(). Try

elm.getText().then(function() { // passing case}, function(err) { // error handling here})

@elgalu

This comment has been minimized.

Show comment
Hide comment
@elgalu

elgalu Mar 3, 2014

Contributor

Thanks @juliemr this is great!!, i'm now able to catch StaleElementReferenceError and retry, with up to 3 times i no longer suffer from flaky tests :)

The only thing i'm missing to make this perfect is to find a way to restart the current protractor run after catching NoSuchWindowError during a failed WebDriver.getCurrentUrl()
There are certain situations in which this happens like when testing in BrowserStack or SauceLabs i get an IE9/IE11 browser crash and having a way to retry the whole protractor run would be great.
Other situation in which this happens is when Safari 7 crashes, sometimes during a input sendKeys o clear() element.

   Stacktrace:
     NoSuchWindowError: Unable to get browser (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 2 milliseconds
Build info: version: '2.39.0', revision: 'ff23eac', time: '2013-12-16 16:11:15'
System info: host: 'AMAZONA-TO69SD6', ip: '208.52.148.82', os.name: 'windows', os.arch: 'x86', os.version: '6.2', java.version: '1.7.0_51'
Session ID: 63181bf7-45d8-4b14-b061-aae3afdf772a
Driver info: org.openqa.selenium.ie.InternetExplorerDriver
Capabilities [{platform=WINDOWS, javascriptEnabled=true, elementScrollBehavior=0, ignoreZoomSetting=false, enablePersistentHover=true, ie.ensureCleanSession=false, browserName=internet explorer, enableElementCacheCleanup=true, unexpectedAlertBehaviour=dismiss, version=11, ie.usePerProcessProxy=false, cssSelectorsEnabled=true, ignoreProtectedModeSettings=false, requireWindowFocus=false, handlesAlerts=true, initialBrowserUrl=http://localhost:39460/, ie.forceCreateProcessApi=false, nativeEvents=true, browserAttachTimeout=0, ie.browserCommandLineSwitches=, takesScreenshot=true}]
==== async task ====
WebDriver.getCurrentUrl()
Contributor

elgalu commented Mar 3, 2014

Thanks @juliemr this is great!!, i'm now able to catch StaleElementReferenceError and retry, with up to 3 times i no longer suffer from flaky tests :)

The only thing i'm missing to make this perfect is to find a way to restart the current protractor run after catching NoSuchWindowError during a failed WebDriver.getCurrentUrl()
There are certain situations in which this happens like when testing in BrowserStack or SauceLabs i get an IE9/IE11 browser crash and having a way to retry the whole protractor run would be great.
Other situation in which this happens is when Safari 7 crashes, sometimes during a input sendKeys o clear() element.

   Stacktrace:
     NoSuchWindowError: Unable to get browser (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 2 milliseconds
Build info: version: '2.39.0', revision: 'ff23eac', time: '2013-12-16 16:11:15'
System info: host: 'AMAZONA-TO69SD6', ip: '208.52.148.82', os.name: 'windows', os.arch: 'x86', os.version: '6.2', java.version: '1.7.0_51'
Session ID: 63181bf7-45d8-4b14-b061-aae3afdf772a
Driver info: org.openqa.selenium.ie.InternetExplorerDriver
Capabilities [{platform=WINDOWS, javascriptEnabled=true, elementScrollBehavior=0, ignoreZoomSetting=false, enablePersistentHover=true, ie.ensureCleanSession=false, browserName=internet explorer, enableElementCacheCleanup=true, unexpectedAlertBehaviour=dismiss, version=11, ie.usePerProcessProxy=false, cssSelectorsEnabled=true, ignoreProtectedModeSettings=false, requireWindowFocus=false, handlesAlerts=true, initialBrowserUrl=http://localhost:39460/, ie.forceCreateProcessApi=false, nativeEvents=true, browserAttachTimeout=0, ie.browserCommandLineSwitches=, takesScreenshot=true}]
==== async task ====
WebDriver.getCurrentUrl()

@elgalu elgalu closed this Mar 3, 2014

@elgalu

This comment has been minimized.

Show comment
Hide comment
@elgalu

elgalu Mar 14, 2014

Contributor

@mcalthrop regarding #610
This is what i do right now to fight against StaleElementReferenceError:

// need this if you get random getText() errors on IE like:
//  Expected '' to be 'invalid login'.
// or StaleElementError in any browser
var waitForElementWithText = function(elm, txt, attempts) {
    if (attempts == null) {
        attempts = 3;
    };

    // first wait for element to be present
    return browser.driver.findElement(elm).then(function(found) {
        // now wait for it to be visible
        browser.manage().timeouts().implicitlyWait(300); // we need this to be faster now
        return browser.driver.wait(function() {
            return found.isDisplayed().then(function(visible) {
                // First wait for it to become visible
                if (visible) {
                    // Then wait for text to be populated
                    return found.getText().then(function(gotTxt) {
                        return gotTxt === txt;
                    });
                } else {
                    browser.sleep(300); // give it a break
                    return false;
                };
            }, function(err) { /* err hnd */
                if (attempts > 0) {
                    return waitForElementWithText(elm, txt, attempts - 1);
                } else {
                    throw err;
                };
            });
        }, browser.params.timeouts.waitBecomeVisible, 'Expectation error: waiting for element to getText(): '+elm);
        // restore implicit wait
        browser.manage().timeouts().implicitlyWait(browser.params.timeouts.implicitlyWait);
    }, function(err) { /* err hnd */
        if (attempts > 0) {
            return waitForElementWithText(elm, txt, attempts - 1);
        } else {
            throw err;
        };
    });
};
Contributor

elgalu commented Mar 14, 2014

@mcalthrop regarding #610
This is what i do right now to fight against StaleElementReferenceError:

// need this if you get random getText() errors on IE like:
//  Expected '' to be 'invalid login'.
// or StaleElementError in any browser
var waitForElementWithText = function(elm, txt, attempts) {
    if (attempts == null) {
        attempts = 3;
    };

    // first wait for element to be present
    return browser.driver.findElement(elm).then(function(found) {
        // now wait for it to be visible
        browser.manage().timeouts().implicitlyWait(300); // we need this to be faster now
        return browser.driver.wait(function() {
            return found.isDisplayed().then(function(visible) {
                // First wait for it to become visible
                if (visible) {
                    // Then wait for text to be populated
                    return found.getText().then(function(gotTxt) {
                        return gotTxt === txt;
                    });
                } else {
                    browser.sleep(300); // give it a break
                    return false;
                };
            }, function(err) { /* err hnd */
                if (attempts > 0) {
                    return waitForElementWithText(elm, txt, attempts - 1);
                } else {
                    throw err;
                };
            });
        }, browser.params.timeouts.waitBecomeVisible, 'Expectation error: waiting for element to getText(): '+elm);
        // restore implicit wait
        browser.manage().timeouts().implicitlyWait(browser.params.timeouts.implicitlyWait);
    }, function(err) { /* err hnd */
        if (attempts > 0) {
            return waitForElementWithText(elm, txt, attempts - 1);
        } else {
            throw err;
        };
    });
};
@mcalthrop

This comment has been minimized.

Show comment
Hide comment
@mcalthrop

mcalthrop Mar 18, 2014

Contributor

Thanks Leo – appreciate that.

It's pretty messy having to do that, isn't it?

@juliemr: this seems to me something that will frequently occur, so I'm wondering if there are any plans to address this at a more fundamental level?

Contributor

mcalthrop commented Mar 18, 2014

Thanks Leo – appreciate that.

It's pretty messy having to do that, isn't it?

@juliemr: this seems to me something that will frequently occur, so I'm wondering if there are any plans to address this at a more fundamental level?

@juliemr

This comment has been minimized.

Show comment
Hide comment
@juliemr

juliemr Mar 20, 2014

Member

Having a retry helper (generically for webdriverJS, not just protractor) would probably be a great addition. No specific plans, but it's on the radar.

Member

juliemr commented Mar 20, 2014

Having a retry helper (generically for webdriverJS, not just protractor) would probably be a great addition. No specific plans, but it's on the radar.

@juliemr

This comment has been minimized.

Show comment
Hide comment
@juliemr

juliemr Mar 20, 2014

Member

Working on a helper module for this here: https://github.com/juliemr/webdriverjs-retry

Member

juliemr commented Mar 20, 2014

Working on a helper module for this here: https://github.com/juliemr/webdriverjs-retry

@elgalu

This comment has been minimized.

Show comment
Hide comment
@elgalu

elgalu Mar 20, 2014

Contributor

nice!!!! 👍

Contributor

elgalu commented Mar 20, 2014

nice!!!! 👍

@mohit-excelindia

This comment has been minimized.

Show comment
Hide comment
@mohit-excelindia

mohit-excelindia Mar 25, 2014

My code

var link = ptor.findElement(protractor.By.linkText('Learn more '));
link.isDisplayed().then(function(isDisplayed){
expect(isDisplayed).toBe(true);
}, function(err){
// throw err;
// console.log ("error in code");
});

In the error handling, for each of the commented line I am getting the same stacktrace.
Can anyone let me know, can I handle the exceptions in better way.

My code

var link = ptor.findElement(protractor.By.linkText('Learn more '));
link.isDisplayed().then(function(isDisplayed){
expect(isDisplayed).toBe(true);
}, function(err){
// throw err;
// console.log ("error in code");
});

In the error handling, for each of the commented line I am getting the same stacktrace.
Can anyone let me know, can I handle the exceptions in better way.

@elgalu

This comment has been minimized.

Show comment
Hide comment
@elgalu

elgalu May 7, 2014

Contributor

@juliemr I'm not sure if keeping this feature externally at webdriverjs-retry is going to help protractor in the long run, I can even see StaleElementReferenceError happening randomly on current protractor unit tests.

Perhaps we should try to address this at a more fundamental level like @mcalthrop suggested?

Contributor

elgalu commented May 7, 2014

@juliemr I'm not sure if keeping this feature externally at webdriverjs-retry is going to help protractor in the long run, I can even see StaleElementReferenceError happening randomly on current protractor unit tests.

Perhaps we should try to address this at a more fundamental level like @mcalthrop suggested?

@sreenalluri

This comment has been minimized.

Show comment
Hide comment
@sreenalluri

sreenalluri Oct 3, 2014

Hi All,
Im stuck with the same "StaleElementReferenceError: stale element reference: element is not attached to the page document" issue. I have a simple configuration like below:

loginPage.js

var loginPage = function() {
var ptor;
ptor = protractor.getInstance();
ptor.get('#/overview');

//signin page
this.username = ptor.findElement(protractor.By.css('input[name="email"]'));
this.password = ptor.findElement(protractor.By.css('input[name="password"]'));
this.signin   = ptor.findElement(protractor.By.css('.btn.btn-primary'));
//signout button
 this.logout = ptor.findElement(protractor.By.xpath('//*[@id=\'logout\']/span/a'));

this.doLogin = function(username, password) {
    this.username.sendKeys(username);
    this.password.sendKeys(password);
    this.signin.click();   
};

this.doLogout = function() {
    this.logout.click();
    ptor.waitForAngular();
};

};
module.exports = new loginPage();

loginTests.js

describe('Clariture Login Tests ::', function() {
var loginPage = require('./loginPage.js');
var ptor;
ptor = protractor.getInstance();

  afterEach( function() {
  });

  it('user should be able to login when valid credentials are provided.', function() {

      loginPage.doLogin('test','test12alpha3');
      expect(browser.getLocationAbsUrl()).toBe(ptor.baseUrl + "#/overview/");

  });

  it('user should be able to logout when clicked on signout icon', function() {

      loginPage.doLogout();
      expect(browser.getLocationAbsUrl()).toBe(ptor.baseUrl + "#/login");

  });

  it('login should fail when in-valid credentials are provided.', function() {

      //ptor.findElement(protractor.By.model('login.username')).sendKeys('test');
      //ptor.findElement(protractor.By.model('login.password')).sendKeys('test');
      //ptor.findElement(protractor.By.css('.btn.btn-primary')).click();

      loginPage.doLogin('test','test12alpha4'); //with invalid password.
      ptor.sleep(1000);
      ptor.switchTo().alert().accept(); //browser alert that password is a mismatch.
      expect(browser.getLocationAbsUrl()).toBe(ptor.baseUrl + "#/login");
  });

});

The last test is failing. I am able to directly call the locators (the ones I commented above) and it works.
But if I use the doLogin method one the first time its called (in test1) is passing and the 3rd test is failing:
StaleElementReferenceError: stale element reference: element is not attached to the page document

Im confused. Any help is much appreciated.

Thanks!

Hi All,
Im stuck with the same "StaleElementReferenceError: stale element reference: element is not attached to the page document" issue. I have a simple configuration like below:

loginPage.js

var loginPage = function() {
var ptor;
ptor = protractor.getInstance();
ptor.get('#/overview');

//signin page
this.username = ptor.findElement(protractor.By.css('input[name="email"]'));
this.password = ptor.findElement(protractor.By.css('input[name="password"]'));
this.signin   = ptor.findElement(protractor.By.css('.btn.btn-primary'));
//signout button
 this.logout = ptor.findElement(protractor.By.xpath('//*[@id=\'logout\']/span/a'));

this.doLogin = function(username, password) {
    this.username.sendKeys(username);
    this.password.sendKeys(password);
    this.signin.click();   
};

this.doLogout = function() {
    this.logout.click();
    ptor.waitForAngular();
};

};
module.exports = new loginPage();

loginTests.js

describe('Clariture Login Tests ::', function() {
var loginPage = require('./loginPage.js');
var ptor;
ptor = protractor.getInstance();

  afterEach( function() {
  });

  it('user should be able to login when valid credentials are provided.', function() {

      loginPage.doLogin('test','test12alpha3');
      expect(browser.getLocationAbsUrl()).toBe(ptor.baseUrl + "#/overview/");

  });

  it('user should be able to logout when clicked on signout icon', function() {

      loginPage.doLogout();
      expect(browser.getLocationAbsUrl()).toBe(ptor.baseUrl + "#/login");

  });

  it('login should fail when in-valid credentials are provided.', function() {

      //ptor.findElement(protractor.By.model('login.username')).sendKeys('test');
      //ptor.findElement(protractor.By.model('login.password')).sendKeys('test');
      //ptor.findElement(protractor.By.css('.btn.btn-primary')).click();

      loginPage.doLogin('test','test12alpha4'); //with invalid password.
      ptor.sleep(1000);
      ptor.switchTo().alert().accept(); //browser alert that password is a mismatch.
      expect(browser.getLocationAbsUrl()).toBe(ptor.baseUrl + "#/login");
  });

});

The last test is failing. I am able to directly call the locators (the ones I commented above) and it works.
But if I use the doLogin method one the first time its called (in test1) is passing and the 3rd test is failing:
StaleElementReferenceError: stale element reference: element is not attached to the page document

Im confused. Any help is much appreciated.

Thanks!

@elgalu

This comment has been minimized.

Show comment
Hide comment
@elgalu

elgalu Oct 3, 2014

Contributor

Stop using findElement in your page objects, replace for example

this.username = ptor.findElement(protractor.By.css('input[name="email"]'));

With

this.username = $('input[name=email]'); // or element(by.css('input[name="email"]')) if you're verbose

Do the same with the rest, retry and if still getting StaleElementReferenceError let us know.

Contributor

elgalu commented Oct 3, 2014

Stop using findElement in your page objects, replace for example

this.username = ptor.findElement(protractor.By.css('input[name="email"]'));

With

this.username = $('input[name=email]'); // or element(by.css('input[name="email"]')) if you're verbose

Do the same with the rest, retry and if still getting StaleElementReferenceError let us know.

@sreenalluri

This comment has been minimized.

Show comment
Hide comment
@sreenalluri

sreenalluri Oct 3, 2014

Hi Leo,
It did work. Im no longer getting the StaleElementReferenceError.
Can you please help me on understanding what it means if I enter the locator inside "$"?

Also I noticed that if I provide the xpath as below, i'm getting the following error:
this.logout = $('//div[@id='logout']/span/a/i');

 InvalidElementStateError: invalid element state: Failed to execute 'querySelectorAll' on 'Document': '//div[@id='logout']/span/a/i' is not a valid selector.

But if I change it to cssSelector, the tests are passing.
this.logout = $('a[title="Sign Out"] > i.fa.fa-sign-out');

Is there a special setup I need to do if I provide xpaths in the $ model?

Much appreciate your help.

Hi Leo,
It did work. Im no longer getting the StaleElementReferenceError.
Can you please help me on understanding what it means if I enter the locator inside "$"?

Also I noticed that if I provide the xpath as below, i'm getting the following error:
this.logout = $('//div[@id='logout']/span/a/i');

 InvalidElementStateError: invalid element state: Failed to execute 'querySelectorAll' on 'Document': '//div[@id='logout']/span/a/i' is not a valid selector.

But if I change it to cssSelector, the tests are passing.
this.logout = $('a[title="Sign Out"] > i.fa.fa-sign-out');

Is there a special setup I need to do if I provide xpaths in the $ model?

Much appreciate your help.

@elgalu

This comment has been minimized.

Show comment
Hide comment
@elgalu

elgalu Oct 3, 2014

Contributor

For xpath selector use by.xpath like this: element(by.xpath(".//tbody[@id='hola']/tr"))
So instead of

$('//div[@id='logout']/span/a/i');

Do:

element(by.xpath('//div[@id=\'logout\']/span/a/i'));
Contributor

elgalu commented Oct 3, 2014

For xpath selector use by.xpath like this: element(by.xpath(".//tbody[@id='hola']/tr"))
So instead of

$('//div[@id='logout']/span/a/i');

Do:

element(by.xpath('//div[@id=\'logout\']/span/a/i'));
@elgalu

This comment has been minimized.

Show comment
Hide comment
@elgalu

elgalu Oct 3, 2014

Contributor

what it means if I enter the locator inside "$"?

Dollar $() is just a shortcut for element(by.css())

Contributor

elgalu commented Oct 3, 2014

what it means if I enter the locator inside "$"?

Dollar $() is just a shortcut for element(by.css())

@sreenalluri

This comment has been minimized.

Show comment
Hide comment
@sreenalluri

sreenalluri Oct 3, 2014

Wow. Thanks. Its working now. One last:
what is the difference between finding the elements in the ways below.
ptor.findElement(protractor.By.model('login.username')); vs
element(by.model('login.username'));

Thanks Again!.

Wow. Thanks. Its working now. One last:
what is the difference between finding the elements in the ways below.
ptor.findElement(protractor.By.model('login.username')); vs
element(by.model('login.username'));

Thanks Again!.

@elgalu

This comment has been minimized.

Show comment
Hide comment
@elgalu

elgalu Oct 3, 2014

Contributor

You're welcome!!
Hank explains findElement vs element(by.xxxx at #1008

Contributor

elgalu commented Oct 3, 2014

You're welcome!!
Hank explains findElement vs element(by.xxxx at #1008

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment