Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Add map() function to element.all #392

Closed
andresdominguez opened this issue Jan 7, 2014 · 11 comments
Closed

Add map() function to element.all #392

andresdominguez opened this issue Jan 7, 2014 · 11 comments

Comments

@andresdominguez
Copy link
Contributor

Some of my tests have to collect the values of multiple elements inside a parent element.

For example, I want to know the labels inside a menu:

Menu
  one
  two
  three

It would be nice to have a map function in element.all:

element.all(by.css('.menu .item')).map(function(item) {
  return item.getText();
}).then(function(labels) {
  expect(labels).toEqual(['one', 'two', 'three']);
});

Map would iterate through each element found with the locator. Then map would resolve all the promises and return a promise with an array of values.

@juliemr
Copy link
Member

juliemr commented Jan 7, 2014

I think we should be able to leverage https://code.google.com/p/selenium/source/browse/javascript/webdriver/promise.js#775 promise.fullyResolved to do this - seems like it would make stuff a lot easier.

@wlingke
Copy link
Contributor

wlingke commented Jan 8, 2014

I would consider my issue #386 resolved with this feature

@konrad-garus
Copy link
Contributor

When I use this map function with any other call on the element (e.g. getInnerHtml(), findElement, findElements), it breaks with "stale element reference". Strangely, getText() works just fine.

Is this the expected behavior? Is something broken in my tests?

@konrad-garus
Copy link
Contributor

That was an issue on my end. Apparently it was running too soon (before data loading / rendering completed), adding ptor.sleep to give it more time solved it.

@konrad-garus
Copy link
Contributor

I also noticed that the mapper has to return a single promise or a value. For instance, returning an array or object that contain promises does not work.

In my use case, I have a ui-calendar with several items. Each item is represented as two spans (for time and title). I wanted to extract its contents into an object:

element.all(by.css('.fc-event-inner')).map(function(el) {
  return {
    time: el.findElement(by.className('fc-event-time')).getText(),
    title: el.findElement(by.className('fc-event-title')).getText()
  }
});

It did not work as expected, because the map does not inspect the object recursively. I had to go for:

element.all(by.css('.fc-event-inner')).map(function(el) {
  function getTime() {
    return el.findElement(by.className('fc-event-time')).getText(),
  }
  function getTitle() {
    return el.findElement(by.className('fc-event-title')).getText(),
  }
  return getTime().then(function(time) {
    return getTitle().then(function(title) {
      return {
        time : time,
        title : title
      };
    });
});

It all makes perfect sense, just something that someone might find useful (and perhaps something that you want to include in docs).

@andresdominguez
Copy link
Contributor Author

@konrad-garus Thanks for the feedback.

I will improve map to resolve an object with multiple promises inside. I will add more test cases for getInnerHtml(), findElement(), etc.

I will also add documentation for the feature.

@konrad-garus
Copy link
Contributor

I'm not sure if that's a good idea. The returned object can be quite complex. Checking for presence of a "then" method anywhere may yield false positives.

That would only be an issue if someone really wanted to shoot themselves in the foot and do something ugly though. I guess not a reason to drop a useful feature, just something to consider. :-)

@andresdominguez
Copy link
Contributor Author

@konrad-garus The change is in v0.17. Can you check if it works for you? Now you should be able to do:

element.all(by.css('.fc-event-inner')).map(function(el) {
  return {
    time: el.findElement(by.className('fc-event-time')).getText(),
    title: el.findElement(by.className('fc-event-title')).getText()
  }
});

I added some documentation in the jsdoc. Better documentation is coming soon.

@konrad-garus
Copy link
Contributor

It does, thank you!

@konrad-garus
Copy link
Contributor

konrad-garus commented Feb 6, 2017

If like me you're revisiting this after a longer break, note that now you need to call el.element(...) instead of el.findElement(...).

@vermin-scripts
Copy link

vermin-scripts commented Nov 16, 2018

i am not able to get value or row outside of function

var trdata;
	 			
	 			$$('#tblAdvanceAdjEntry tbody tr').map(function(el)
	 			{
	 				return el.getText().then(function(row1){
	 					return row1;
	 				});

	 			}).then(function(row){

	 					console.log("rows --->"+row.length);
	 					trdata = row.length;
	 					
	 					console.log('trdata --'+trdata);
	 					var r = trdata - 2;
	 					element(by.xpath('//*[@id="'+r+'_AdjAgainstAmt"]')).sendKeys(adjsamt);
	 					return row.length;
	 			});
console.log("result "+trdata);//getting value of row here but it returns undefine

`
```
	 			

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants