Protractor: Protractor hangs when returning ElementFinders or WebElements from `map()`

Created on 9 Jun 2015  Â·  34Comments  Â·  Source: angular/protractor

Test setup:
Non - Angular page, with browser.ignoreSynchronization = true
Browser: IE9
Protractor: 2.1.0
webdrivers up to date

Description:
In the below code I am using element.all within Page Objects to find elements. When run the test will hang indefinitely. This happens when I try to assign a ElementFinder from the array to a member variable (so I can have reference for future use).

If I comment the following two lines then the tests will run to completion.

self.rowWebElement = el;  // commenting this line will not hang
  And
'RowWebElement': el.getWebElement(), // commenting this line will not hang

So why is it hanging?

Code:

var TablePage = function() {
  this.uri = "http://www.w3schools.com/html/html_tables.asp";

  this.open = function() {
    browser.get(this.uri);
  };

  this.getPageResults = function() {
    var self = this;
    var rows = element.all(by.className('reference'));
    this.results = rows.map(function(el, idx) {
      console.log('Processing row');

      self.rowWebElement = el;  // commenting this line will not hang
      var cols = el.all(by.tagName('td'));

      return {
       'Number': cols.get(0).getText(),
       'First Name': cols.get(1).getText(),
       'Last Name': cols.get(2).getText(),
       'Points': cols.get(3).getText(),
       'RowWebElement': el.getWebElement(), // commenting this line will not hang
       'Index': idx
      }
    });
    return this.results;
  };

  this.getRow = function(fName) {
    var self = this;
    var deferred = protractor.promise.defer();

    var filtered = [];

    self.results.then(function (grid) {
      for (var i=0; i < grid.length; i++) {
        var result = grid[i]['First Name'];
        if (result == fName ) {
          console.log('Row matching criteria :' + result);
          filtered.push(grid[i]);
        }
      }
      deferred.fulfill(filtered[0]);
    });

    return new TableRowObjectPromise(deferred.promise);
  };

};

var TableRowObjectPromise = function (trPromise) {
  // Wrap the promise
  var self = this;
  this.getValue = function(colName) {
    return trPromise.then(function(tr) {
      return tr[colName];
    });
  };
};

/** ----------------------------------------------------------------
 *  begining of test 
 */ 
var tablePage = new TablePage();
// Tests begin
describe('Table page', function() {

  beforeAll(function() {
    browser.ignoreSynchronization = true
  });

  it('- goto table page', function() {
    tablePage.open();
  });

  it('- get table row objects', function() {
    var pr1 = tablePage.getPageResults();
    var tr = tablePage.getRow('Eve');
  });
});
untriaged polish bug

Most helpful comment

We are also seeing this with v5.3.2, this is in an environment where we use the new async/await and control flow disabled.

All 34 comments

Are you saying if you comment _either_ line the test will run? Or that you have to comment both?

Both lines need to be commented out for this script to run.

So basically assigning either the ElementFinder or the underlying webelement(from getWebElement()) causes the script to hang.

Do you know where the hang occurs? On the getWebElement line?

Also: Do you have this problem with other browsers?

In chrome the issue happens slightly differently:

  1. This line 'RowWebElement': el.getWebElement(), causes the protractor to hang indefinitely
  2. The other line self.rowWebElement = el; has no effect. Even if this line is present the protractor terminates finally provided the line in step 1 is commented.

I couldn't get the exact place where it hangs. but I have a feeling it is actually in the this.getRow function that it is not able to handle something. Update : Please my comment below

The output of the above script:

PS c:\test> protractor.cmd .\ie_conf.js
Using the selenium server at http://localhost:4444/wd/hub
[launcher] Running 1 instances of WebDriver
Spec started
Started

  Table page
    ✓ - goto table page
.Processing row
Processing row

And this is my conf.js:

exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',

  capabilities: {
    'browserName': 'chrome',
  },

  specs: ['example.js'],

  framework: 'jasmine2',
  onPrepare: function() {

    var SpecReporter = require('jasmine-spec-reporter');
    // add jasmine spec reporter
    jasmine.getEnv().addReporter(new SpecReporter({
      displayStacktrace: 'none',
      customProcessors: []
    }));
  },

  // Options to be passed to Jasmine-node.
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 40000
  }
};

The freeze could be occurring at the getWebElement() at least in chrome. Here is a reduced script which causes the freeze:

var TablePage = function() {
  this.uri = "http://www.w3schools.com/html/html_tables.asp";

  this.open = function() {
    browser.get(this.uri);
  };

  this.getPageResults = function() {
    var self = this;
    var rows = element.all(by.className('reference'));
    this.results = rows.map(function(el, idx) {
      console.log('Processing row');

      // self.rowWebElement = el;  // commenting this line will not hang
      var cols = el.all(by.tagName('td'));

      return {
       'Number': cols.get(0).getText(),
       'First Name': cols.get(1).getText(),
       'Last Name': cols.get(2).getText(),
       'Points': cols.get(3).getText(),
       'RowWebElement': el.getWebElement(), // commenting this line will not hang
       'Index': idx
      }
    });
    return this.results;
  };
};

// ----------------------------------------------------------------
var tablePage = new TablePage();
// Tests begin
describe('Table page', function() {

  beforeAll(function() {
    browser.ignoreSynchronization = true
  });

  it('- goto table page', function() {
    tablePage.open();
  });

  it('- get table row objects', function() {
    var pr1 = tablePage.getPageResults();
  });
});

Can you show me your HTML?

Actually it is the publicly accessible page. The url is in the
this.uri line.

Also the script can be executed as is. No other dependencies.
On Jun 10, 2015 7:52 PM, "Sammy Jelin" [email protected] wrote:

Can you show me your HTML?

—
Reply to this email directly or view it on GitHub
https://github.com/angular/protractor/issues/2227#issuecomment-110948518
.

Can you test if this is still an issue after [email protected]? Updates to the control flow have probably changed hanging behavior.

@juliemr I can still (with protractor 2.4.0) reliably reproduce the problem in the linked SO topic above. It's hanging as before. Thanks!

Hi, not to be a +1 guy, but I'm hitting the same thing (I think). I've reduced it to something that looks like

someElement.all(someSelector)
   .map(function(el) {
        return { theElement : el };
    });

I'm using protractor 2.5.1, and webdriver-manager start says:

14:44:17.962 INFO - Java: Oracle Corporation 25.60-b23
14:44:17.962 INFO - OS: Linux 3.19.0-30-generic amd64
14:44:17.967 INFO - v2.47.1, with Core v2.47.1. Built from revision 411b314

I'm using chrome on linux. If I can give any more useful information please let me know (or, if there's a current workaround I can't seem to find...)

Thanks!

Edit: Is there a usual way to build a plunkr or something to give repro? A blessed starting point?

I'm relatively new to github and therefore I'm unsure of the general sentiment towards, as @hoopes puts it, "+1 guy[s]", but I am also running into this problem, with the exact same logic as he mentions above, but using Chrome on Windows Server 2008 R2. Same version of protractor and same info on webdriver-manager start.

Hi, I am hope that the following information can help. I also came across the problem as it seems (as @hoopes showed above) that is can be reduced down the access the element in the return JSON of the .map. It seems as though that it is not just the specific element being mapped, but any element. For exampe:

someElement.all(someSelector)
   .map(function(el) {
        return { theElement : el };
    });

will not work and neither does:

var elements = someElement.all(someSelector);
   elements.map(function(el, index) {
        return { theElement : elements.get(index) };
    });

Nor:

var elements = someElement.all(someSelector);
   elements.map(function(el, index) {
        return { theElement : someElement.all(someSelector).get(index) };
    });

It seems as though this is related only the period of time of the .map process. The following code (or seemed to work fine):

var elements = someElement.all(someSelector);
   elements.map(function(el, index) {
        return { index: index,
                     theElement : function(){
                                          return elements.get(this.index);
                                          }
                 };
    });

I was then able to use that element somewhere else like:

browser.elements[i].theElement().click();

I am also using protractor 2.5.1
INFO - Java: Oracle Corporation 24.65-b04
INFO - OS: Linux 3.13.0-37-generic amd64
INFO - v2.45.0, with Core v2.45.0. Built from revision 5017cb8

OK. The original problem by ravikumars is no longer present in Protractor 3.0. I've tried both scripts.

Edit: I'm wrong. Here's an actual reduced test case that hangs with protractor@3

This is almost certainly something to do with the control flow getting locked because a web element is a promise like object.

describe('Table page', function() {

  beforeAll(function() {
    browser.ignoreSynchronization = true
  });

  beforeEach(function() {
    browser.get("http://www.w3schools.com/html/html_tables.asp")
  });

  it('- get table row objects', function() {
    var rows = element.all(by.css('h2'));
    rows.map(function(el, idx) {
      console.log('Processing row');

      return {
       'webElement': el.getWebElement(), // commenting this line will not hang
       'foo': 'bar'
      }
    });
  });
});

Same problem here. After playing a little - found short reproducible code

$$('.audio-player-item').map(function (elemt, index) {
            return {param: elemt};
     });

But with this - works fine:

$$('.audio-player-item').map(function (elemt, index) {
            return {param: elemt.getText()};
     });

This is not good. Operations like map should not fail like this for basic use-cases.

Is there a reason why map() is using webdriver.promise.fullyResolve(), instead of webdriver.promise.when()?

https://github.com/angular/protractor/blob/master/lib/element.ts#L519

I experimented with changing it to when and didn't get any failures in the Protractor test suite.

Needing to resolve every nested promise-like property of every mapped object does not seem like a common requirement. But mapping to an object with an ElementFinder field _does_ seem like a common requirement. And I'm not sure why you would want the full resolution to happen _automatically_? It seems like very non-obvious API design.

I would contend that it is much better to remove the fullyResolve() call and fix this bug. If users need this behaviour, they can always call webdriver.promise.fullyResolve() themselves on whatever they intend to return from the map function.

Hi, guys and Julie!
I wanna show my own cases , where I catched troubles with map()

it('good expectations', function () {
  element
    .all(by.repeater('l in codes.helper.list track by l.id'))
    .map(function (item, idx) {
      return item.$$('td').get(2).getText();
    })
    .then(function (list) {
      expect(list.length).toBe(10);     //  <<--  both expectations are correct
      expect(list[1]).toBe('5625-2811-4354');
    })
  ;
});
it('bad expectations', function () {
  element
    .all(by.repeater('l in codes.helper.list track by l.id'))
    .map(function (item, idx) {
      return item;
    })
    .then(function (list) {
      expect(list.length).toBe(10);     //  <<--  both expectations are incorrect
      expect(list[1].$$('td').get(2).getText()).toBe('5625-2811-4354');
    })
  ;
});

Hi again!

Also I want to say, another collection function, "ElementArrayFinder.prototype.reduce", is working properly.
And you can replace "map" with "reduce".

For example, this code will working

            fdescribe('"ElementArrayFinder.prototype.reduce" test.', function () {
              var list;

              beforeEach(function (done) {
                list = element(by.tagName('md-radio-group'))
                  .all(by.tagName('md-radio-button'))
                  .reduce(function (acc, el, idx) {
                    acc.push(el);
                    return acc;
                  }, [])
                  .then(function (_list) {
                    list = _list;
                    done();
                  })
                ;
              });

              fit('List should have a length', function () {
                expect(list.length).toBe(3);
              });

              fit('List should have "ElementFinder"-elements', function () {
                expect(list[0].locator).toBeDefined();
              });

              fit('1st list should have a "role"-attr', function () {
                expect(list[0].getAttribute('role')).toBe('radio');
              });
            });

That's whay I think , problem with "map" is not conceptual. It looks like some subtle bug.

@jonrimmer sorry for the slow response here - I tried changing to when and tests DO fail - this stops working:

  it('should allow using protractor locator within map', function() {
    browser.get('index.html#/repeater');

    var expected = [
        { first: 'M', second: 'Monday' },
        { first: 'T', second: 'Tuesday' },
        { first: 'W', second: 'Wednesday' },
        { first: 'Th', second: 'Thursday' },
        { first: 'F', second: 'Friday' }];

    var result = element.all(by.repeater('allinfo in days')).map(function(el) {
      return {
        first: el.element(by.binding('allinfo.initial')).getText(),
        second: el.element(by.binding('allinfo.name')).getText()
      };
    });

    expect(result).toEqual(expected);
  });

We want to be able to fully resolve because we'd like to handle promises-inside-objects like above. But you can't fully resolve a WebElement or ElementFinder.

I think this probably needs to be fixed at the webdriver fullyResolved level.

Any chance for a workaround of the issue?

This is now working with the latest version of Protractor.

Still seeing the hang on Protractor 4.0.14.

const divs = element.all(by.css("div.my-div"));

divs.reduce((array, div) => array.concat({myDiv: div}), []); // WORKS

divs.map(div => { return {myDiv: div}; }); // HANGS

@juliemr Yeah, sorry, I'm still seeing the hang with the same repro steps. I'm using protractor 4.0.9 at the moment. Is there a common way to show this so we're talking about the same thing, at least? :)

Like...maybe a jsfiddle, and a test to run against it?

Quick edit: Got this error message, not sure if it's helpful...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory

<--- Last few GCs --->

  104485 ms: Mark-sweep 1316.6 (1457.1) -> 1316.6 (1457.1) MB, 2475.2 / 0 ms [allocation failure] [GC in old space requested].
  106951 ms: Mark-sweep 1316.6 (1457.1) -> 1316.6 (1457.1) MB, 2465.8 / 0 ms [allocation failure] [GC in old space requested].
  109454 ms: Mark-sweep 1316.6 (1457.1) -> 1311.9 (1457.1) MB, 2503.0 / 0 ms [last resort gc].
  111921 ms: Mark-sweep 1311.9 (1457.1) -> 1311.5 (1457.1) MB, 2466.9 / 0 ms [last resort gc].

<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x3eb21aab4629 <JS Object>

    1: set [native weak-collection.js:~36] [pc=0xadef76b6d8c] (this=0x243e3348c1a1 <JS WeakMap>,j=0x27b20c654829 <a ManagedPromise with map 0x1d3d581c7609>,l=1474339)

    2: getUid(aka getUid) [/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:626] [pc=0xadef7611f12] (this=0x3eb21aa041b9 <undefined>,obj=0x27b20c654829 <a ManagedPromise with map 0x1d3d581c7609...

Issue is still reproduced in v4.0.14.

Can someone suggest nice workaround? I am trying to develop lib for custom fragments/ fragmentsArrays, and it seems like i am almost done with that. But i can't inherit ElementArrayFinder properly.

Would be thankful for any help! During that time, will try to prepare pull request with fix, i just need to understand where the problem is

BTW: still reproducible on 5.x

@Xotabu4 , I wrote about workaround about half a year ago )
https://github.com/angular/protractor/issues/2227#issuecomment-233927979

Still seeing this with Protractor 5.1.2. @juliemr please reopen.

Still seeing this with Protractor 5.1.2. @juliemr please reopen.

By the way, here is the hotfix we are using in our tests:
in protractor.conf file inside onPrepare:

require("protractor").ElementArrayFinder.prototype.map = function(mapFn) {
    return this.reduce((arr, el) => arr.concat(mapFn(el, arr.length)), []);
};

Thanks @korobochka, I'll try that.

Thank you for the workaround/patches above, I'm on 5.2.0 and was experiencing this issue.

all(by.css("...")).map((item) => { item: item, title: item.getText() });

We are also seeing this with v5.3.2, this is in an environment where we use the new async/await and control flow disabled.

Was this page helpful?
0 / 5 - 0 ratings