4
votes

I am getting the error below as a result of using the each() method in protractor. It has worked fine in the past but is now consistently failing with this error.

Failed: stale element reference: element is not attached to the page document

element.all(bars).each((element) => element.getCssValue('width'))

Is there an alternative or a reason why this might be?

(For clarity, all I want to do is to get the width of each element in a set of web elements called bars.)

Thanks!

4
Are you executing this after the page has been completely loaded?trincot
Yes, I check that a loading wrapper has disappeared before executing. browser.wait(EC.invisibilityOf(element(loadingWrapper)), 6000);Isabella
But that code does not stop javascript execution. You should probably use the promise returned by browser.wait and only when it resolves do the element.all(bars).each loop.trincot
Hi @trincot thanks, for your help. It ended up being a timing issue. I added a .then(() => browser.driver.sleep(1000)) and it now works perfectly. It confused me initially because it had worked perfectly in the past. I think this must be down to network delays or priorities in protractor. Thanks again :)Isabella
This sounds like a hacky solution. It would be better to wait for browser.wait to resolve.trincot

4 Answers

4
votes

In a nutshell, it happens because each() just fires commands simultaneously against all elements. In your case you probably need to go this way element.all(bars).getCssValue('width')).then(array => {/*do what you want with returned array here*/})

* Edited *

What you want to do is

element.all(bars).getCssValue('width')).then(array => {
    // tests is at least one element will not be "0px"
    let elementHasWidth = array.some(elem => elem !== "0px");
    expect(elementHasWidth).toBe(true);
})
3
votes

New problems like this with existing code are likely due to Protractor and Selenium evolving from Web Driver Control Flow and WebDriverJS Promise Manager to native promises. You used to be able to write code that looked like synchronous code and under the hood the toolkits would convert it to asynchronous code that waited for the DOM to load and JavaScript on the page to run. Going forward you need to convert your code to explicitly use async/await or promises. (See the reasoning behind the Selenium change here.)

Unfortunately a lot of old (pre 2019) examples, even in the Protractor documentation, are written in synchronous style, so you should be careful about following them. Even more unfortunately, you cannot mix async code with Control Flow code, and if you try, all the Control Flow will be disabled, and probably some of your tests that relied on it will start failing.

By the way, what is the value of bars? A Protractor by object works differently than a native WebDriver locator and I'm not sure they are reusable. Try using by.name('bars') or whatever instead of bars.

Your case is tricky because of all the promises involved. element.getCssValue returns a promise. Since you are trying to get a true or false value out of this, I suggest using a reducer.

let nonZero = element.all(by.name('bars')).reduce((acc, elem) => {
  return acc || elem.getCssValue('width').then( (width) => width > 0 );
}, false);

In a more complicated situation, you could use all().each() but you have to be careful to ensure that nothing you do inside each affects the DOM, because once it does, it potentially invalidates the rest of the array.

If you are potentially modifying the page with your ultimate action, then, as ugly as it may seem, you need to loop over finding the elements:

for (var i = 0; true; i++) {
  let list = element.all(by.css('.items li'));
  if (i >= await list.count();)
    break;
  list.get(i).click();
};
1
votes

Try using this code before your error statement:

  browser.wait(function() {
        return element.all(bars).isPresent().then(function(result) {
            return result;
        });
    }, 5000);
0
votes

Thanks to everyone who helped. The solution was retry again if it resulted in error. Due to the realtime nature of the element, the element reference was changing before it had got the css width.