Two loops for into stream



I have some String method with two for and if:

public String getCellValue(String stringFirst, int integerFirst) {
    List<WebElement> tableRows = driver.findElements(By.xpath("//table[@id='dt']/tbody/tr"));

    int index = 0;
    WebElement dataCell = null;

    for (WebElement row : tableRows) {
        index++;

        List<WebElement> tableCells = row.findElements(By.tagName("td"));

        for (WebElement cell : tableCells) {

            if (getTextFromElement(cell).equals(stringFirst)) {

                dataCell = driver.findElement(By.xpath(String.format("//table[@id='dt']/tbody/tr[%s]/td[%s]", index, integerFirst)));
            }
        }
    }

    return getTextFromElement(dataCell);
}

And I still try to change this implementation to some stream. Is it possible in this case? I’ve tried many options, but I have a problem with indexing (index++;) and then with filter in tableCells By.tagname. I think that is it not possible to change that full method to stream, but I don’t know. Could you help me with some implementation?
Thanks a lot.

Answer

Understanding given data:

To get all rows:

driver.findElements(By.xpath("//table[@id='dt']/tbody/tr"))

To get columns(cells) for a given row:

row.findElements(By.tagName("td"))

To get the text for a given column(cell):

getTextFromElement(cell)

Result expected if the text on cell at row index matches given string(stringFirst):

driver.findElement(By.xpath(String.format("//table[@id='dt']/tbody/tr[%s]/td[%s]", index, integerFirst)))  

From the requirements, it seems like logic should be around indexes.

Breakdown of approach:

Firstly, we need a list of rows. This is exactly the same as before:

List<WebElement> tableRows = driver.findElements(By.xpath("//table[@id='dt']/tbody/tr"));

Now we’ll create an IntStream of all indexes.
rangeClosed means ends are inclusive
boxed() is used to convert primitive ints to Integer

IntStream.rangeClosed(1,tableRows.size()).boxed()
1,
2,
3,
...

For each index idx, create a map with idx as key and columns corresponding to row idx as value:

.map(idx -> Map.of(idx, list.get(idx-1).findElements(By.tagName("td"))))
{1=[cell11, cell12, cell13]},
{2=[cell21, cell22, cell23]},
...

Merging all the maps:

.flatMap(m -> m.entrySet().stream())
1=[cell11, cell12, cell13],
2=[cell21, cell22, cell23],
...

Filter out the values that do not contain cell with text as a given string:
findAny() returns Optional if any value is found(not sure if that is the requirement)

.filter(containsCellWithText).findAny()

Implementation of the above Predicate:

Predicate<Map.Entry> containsCellWithText = (Map.Entry me) -> me.getValue()
                                                                .stream()
                                                                .anyMatch(cell -> getTextFromElement(cell).equals(stringFirst));

Now, only desired entries remain. Fetch the result for the corresponding index:

.map(me -> driver.findElement(By.xpath(String.format("//table[@id='dt']/tbody/tr[%s]/td[%s]", me.getKey(), integerFirst))))

We should have the answer in Optional(if exist). Fetch it or else get an empty string:

.orElse("")

Complete answer put together:

List<WebElement> tableRows = driver.findElements(By.xpath("//table[@id='dt']/tbody/tr"));
        
Predicate<Map.Entry> containsCellWithText = (Map.Entry me) -> me.getValue()
                                                                .stream()
                                                                .anyMatch(cell -> getTextFromElement(cell).equals(stringFirst));
        
IntStream.rangeClosed(1,tableRows.size())
         .boxed()
         .map(idx -> Map.of(idx, list.get(idx-1).findElements(By.tagName("td"))))
         .flatMap(m -> m.entrySet().stream())
         .filter(containsCellValue)
         .findAny()
         .map(me -> driver.findElement(By.xpath(String.format("//table[@id='dt']/tbody/tr[%s]/td[%s]", me.getKey(), integerFirst))))
         .orElse("")

Resources:



Source: stackoverflow