I’d like to start with the following service announcement,

you really shouldn’t need this.

That being said; I’ve started using a third party component library which led to a use-case for this. It so happened that I was creating a page object for a component library called ag-grid. At first glance I thought nothing special about it; and that I could retrieve rows normally. ag-grid however supports the pinning of columns to the left and/or right side of the list. And these rows then fall inside their own respective markups. Now you can easily create a structure allowing me to target the (non-)pinned columns. But for ease of use being able to get full set of cells can also be useful and might be more suited for the test case. This led to the creation of the following code.

const aggregate = (...locators: ElementArrayFinder\[\]): ElementArrayFinder => {
  const deferredAggregate = protractor.promise.defer();
  const combinedLocator = {
    toString: function () {
      return this.locators.map(locator => locator.toString()).join(', ');

    .all(locators.map(locator => locator.getWebElements()))
    .then(results => {
      deferredAggregate.fulfill(results.reduce((a,b) => a.concat(b)));

  return new ElementArrayFinder(null, () => deferredAggregate.promise, combinedLocator);

As you can see it returns a new ElementArrayFinder which is the return type for the all locator and thus what we need for the aggregate. In order to create the aggregate response it resolves each locator’s getWebElements response through Promise.all. It then fulfils the deferredAggregate with the promised results reduced to a single WebElement array. In this implementation the combinedLocator.toString is also supplied. This is to ensure that feedback for this aggregation also contains all locators in the message. For the creation of the ElementArrayFinder itself it is not required though