Interacting with the throw Stale Element Reference

I have this site: http://embed.plnkr.co/Bs5iDqtXSSnvye2ORI6k/preview

code:

var app = angular.module('plunker', []);

var a = new Array(1000);

for (var i = 0; i< 1000; i++) {
  a[i] = 'Name' + i;
}

app.controller('MainCtrl', function($scope, $interval) {
  $scope.names = a;

  $scope.start = function () {
    $interval(function () {
      $scope.names.pop();
    }, 50);
  }
});

      

And the following specification:

'use strict';

describe('Name list', function () {
    it('should get the text of the last name', function () {
        browser.driver.get('http://embed.plnkr.co/Bs5iDqtXSSnvye2ORI6k/preview');
        browser.switchTo().frame(browser.driver.findElement(protractor.By.tagName('iframe')));
        element(by.buttonText('start')).click();
        expect(element.all(by.tagName('span)).last().getText()).toBe('Name999');
    });
});

      

And this config:

'use strict';

// An example configuration file.
exports.config = {
    baseUrl: 'http://localhost:3000',
    seleniumAddress: 'http://localhost:4444/wd/hub',
    specs: ['stale.spec.js']
};

      

And when I run Protractor I get the following error:

StaleElementReferenceError: deprecated element reference: element not attached to page document (session info: chrome = 43.0.2357.81)
( Driver info: chromedriver = 2.15.322455 (ae8db840dac8d0c453355d3d922c91adfb61df8f) (WARNING: xml platform = Mac OS X86) server did not provide any stacktrace information) Command duration or timeout: 9 milliseconds. For documentation on this error, please visit: http://seleniumhq.org/exceptions/stale_element_reference.htmlBuild information: version: '2.45.0', version: '5017cb8', time: '2015-02-26 23:59:50' System information: host: "ITs-MacBook-Pro.local", ip: '129.192 .20.150 ', os.name:' Mac OS X ', os.arch:' x86_64 ', os.version: '10 .10.3', java.version: '1.8.0_31' Driver Information: Org.openqa Capabilities. selenium.chrome.ChromeDriver [{applicationCacheEnabled = false, rotatable = false, mobileEmulationEnabled = false, chrome = {UserDataDir = / var / folders / rr / 63848xd90yscgwpkfn8srbyh0000gq / t / .org.chromium. trueChromium. = false, handleAlerts = true, version = 43.0.2357.81, platform = MAC, browserConnectionEnabled = false, nativeEvents = true, acceptSslCerts = true, locationContextEnabled = true, webStorageEnabled = true, browserName = chrome,takesScreenshot = true, javascriptEnabled = true, cssSelectorsEnabled = true}] Session id: 235ec977a69d98c7f5b75a329e8111b2

This means that the element I'm trying to interact with (getting the element's text) is no longer bound to the DOM. This example is really my spec simplied. What is really going on in my real spec, I am trying to get the text of the last item of the list of items (generated by ng-repeat). It also happens that the model is updated by removing the last element of the array representing the list of elements. This example above is just something to reproduce the error (every time).

If I comment this line: element(by.buttonText('start')).click();

BOM completed successfully.

+3


source to share


2 answers


I have struggled with this a lot and tried to understand why this would happen. At first I thought that the element that points to the last element of the list was created long before the interaction took place, so it didn't surprise me that the element could be detached from the DOM in the time between the creation of the element crawler and the interaction.

What I learned later is that the element is found just before the interaction takes place, every time you interact with something. Therefore, pointing to the last element should actually point to the last element in time interacting with the element.

By using browser.pause()

I was able to see what WebDriver is actually doing and these are the two tasks between which the error occurs:

(pending) Task::414<WebDriver.findElements(By.tagName("span"))>
| | | | | | Task: WebDriver.findElements(By.tagName("span"))
| | | | | |     at Array.forEach (native)

      



Here in-between, the DOM is updated according to the model and the last element of the list is stripped off.

(pending) Task::1170<WebElement.getText()>
| | | | | | Task: WebElement.getText()
| | | | | |     at Array.forEach (native)

      

The DOM is updated in this little execution hole. Currently the model is refreshed every 50ms and this will definitely result in a Stale Element Reference error. But if I increase the interval to, say 1000ms, then the chances of getting an error are much less. It depends on how fast your computer is running if you get this error.

The fix is ​​up to you, but with this information, I hope it will be a little clearer what to do.

+5


source


The browser runs asynchronously from your protractor test. This example really emphasizes that it is beautiful (its problem for many testers, but not so obvious). This is compounded by what looks like a single line:

expect(element.all(by.tagName('span')).last().getText()).toBe('Name999');

      

in fact, it takes a few hits to the browser (there are many Promises, which are returned and settled: element.all

, last

, getText

). For most web pages that are "passive" once they have stabilized, this is not a problem, but for any web page that changes dynamically without user inputs, testing with a protractor can be painful.

To make the search and test "atomic" in the browser, thereby discarding this problem, you can define a function to run in the browser (my CSS DOM-fu is weak, so hopefully someone can tweak this to make it less scary) :



expect(browser.executeScript(function() {
   var spans = document.getElementsByTagName('span');
   var lastSpan = spans[spans.length - 1]; // XXX handle empty spans
   return lastSpan.innerText;
}).toBe('Name999');

      

Beware that the "function" is serialized and executed in the browser, so variables from the enclosing scope will not work. Also, this approach loses any browser compatibility magic lurking in the protractor or webserver (for example, I wouldn't be surprised if getText()

not just an accessory innerText

).

Also note that you still have a race condition. In between the 'click' to get it started and this code actually checked the DOM, it may or may not be mutated (and "Name999" may be missing).

+1


source







All Articles