diff --git a/lib/protractor.js b/lib/protractor.js
index f6f745563..2ce52faa9 100644
--- a/lib/protractor.js
+++ b/lib/protractor.js
@@ -327,6 +327,92 @@ var buildElementHelper = function(ptor) {
});
};
+ /**
+ * Apply a filter function to each element found using the locator. Returns
+ * promise of a new array with all elements that pass the filter function. The
+ * filter function receives the ElementFinder as the first argument
+ * and the index as a second arg.
+ *
+ * @alias element.all(locator).filter(filterFn)
+ * @view
+ *
+ * - First
+ * - Second
+ * - Third
+ *
+ *
+ * @example
+ * element.all(by.css('.items li')).filter(function(elem, index) {
+ * return elem.getText().then(function(text) {
+ * return text === 'Third';
+ * });
+ * }).then(function(filteredElements) {
+ * filteredElements[0].click();
+ * });
+ *
+ * @param {function(ElementFinder, number): webdriver.WebElement.Promise} filterFn
+ * Filter function that will test if an element should be returned.
+ * filterFn should return a promise that resolves to a boolean.
+ * @return {!webdriver.promise.Promise} A promise that resolves to an array
+ * of ElementFinders that satisfy the filter function.
+ */
+ ElementArrayFinder.prototype.filter = function(filterFn) {
+ return this.asElementFinders_().then(function(arr) {
+ var list = [];
+ arr.forEach(function(elementFinder, index) {
+ filterFn(elementFinder, index).then(function(satisfies) {
+ if (satisfies) {
+ list.push(elementFinder);
+ }
+ });
+ });
+ return list;
+ });
+ };
+
+ /**
+ * Apply a reduce function against an accumulator and every element found
+ * using the locator (from left-to-right). The reduce function has to reduce
+ * every element into a single value (the accumulator). Returns promise of
+ * the accumulator. The reduce function receives the accumulator, current
+ * ElementFinder, the index, and the entire array of ElementFinders,
+ * respectively.
+ *
+ * @alias element.all(locator).reduce(reduceFn)
+ * @view
+ *
+ * - First
+ * - Second
+ * - Third
+ *
+ *
+ * @example
+ * var value = element.all(by.css('.items li')).reduce(function(acc, elem) {
+ * return elem.getText().then(function(text) {
+ * return acc + text + ' ';
+ * });
+ * });
+ *
+ * expect(value).toEqual('First Second Third ');
+ *
+ * @param {function(number, ElementFinder, number, Array.)}
+ * reduceFn Reduce function that reduces every element into a single value.
+ * @param {*} initialValue Initial value of the accumulator.
+ * @return {!webdriver.promise.Promise} A promise that resolves to the final
+ * value of the accumulator.
+ */
+ ElementArrayFinder.prototype.reduce = function(reduceFn, initialValue) {
+ var valuePromise = webdriver.promise.fulfilled(initialValue);
+ return this.asElementFinders_().then(function(arr) {
+ arr.forEach(function(elementFinder, index) {
+ valuePromise = valuePromise.then(function(value) {
+ return reduceFn(value, elementFinder, index, arr);
+ });
+ });
+ return valuePromise;
+ });
+ };
+
/**
* The ElementFinder can be treated as a WebElement for most purposes, in
* particular, you may perform actions (i.e. click, getText) on them as you
diff --git a/spec/basic/elements_spec.js b/spec/basic/elements_spec.js
index 1341181ee..9808b3469 100644
--- a/spec/basic/elements_spec.js
+++ b/spec/basic/elements_spec.js
@@ -231,6 +231,37 @@ describe('ElementFinder', function() {
expect(labels).toEqual([1, 2, 3, 4, 5, 6, 7]);
});
+ it('should filter elements', function() {
+ browser.get('index.html#/form');
+ var count = element.all(by.css('.menu li a')).filter(function(elem) {
+ return elem.getText().then(function(text) {
+ return text === 'bindings';
+ });
+ }).then(function(filteredElements) {
+ return filteredElements.length;
+ });
+
+ expect(count).toEqual(1);
+ });
+
+ it('should reduce elements', function() {
+ browser.get('index.html#/form');
+ var value = element.all(by.css('.menu li a')).
+ reduce(function(currentValue, elem, index, elemArr) {
+ return elem.getText().then(function(text) {
+ return currentValue + index + '/' + elemArr.length + ': ' + text + '\n';
+ });
+ }, '');
+
+ expect(value).toEqual('0/7: repeater\n' +
+ '1/7: bindings\n' +
+ '2/7: form\n' +
+ '3/7: async\n' +
+ '4/7: conflict\n' +
+ '5/7: polling\n' +
+ '6/7: animation\n');
+ });
+
it('should export an isPresent helper', function() {
browser.get('index.html#/form');