package net.thucydides.core.pages.components; import ch.lambdaj.function.convert.Converter; import net.thucydides.core.matchers.BeanMatcher; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import java.util.*; import static ch.lambdaj.Lambda.convert; /** * Class designed to make it easier reading from and reasoning about data in HTML tables. */ public class HtmlTable { private final WebElement tableElement; private List<String> headings; public HtmlTable(final WebElement tableElement) { this.tableElement = tableElement; this.headings = null; } public HtmlTable(final WebElement tableElement, List<String> headings) { this.tableElement = tableElement; this.headings = headings; } public static HtmlTable inTable(final WebElement table) { return new HtmlTable(table); } public List<Map<Object, String>> getRows() { List<Map<Object, String>> results = new ArrayList<Map<Object, String>>(); List<String> headings = getHeadings(); List<WebElement> rows = getRowElementsFor(headings); for (WebElement row : rows) { List<WebElement> cells = cellsIn(row); if (enoughCellsFor(headings).in(cells)) { results.add(rowDataFrom(cells, headings)); } } return results; } public WebElement findFirstRowWhere(final BeanMatcher... matchers) { List<WebElement> rows = getRowElementsWhere(matchers); if (rows.isEmpty()) { throw new AssertionError("Expecting a table with at least one row where: " + Arrays.deepToString(matchers)); } return rows.get(0); } public boolean containsRowElementsWhere(BeanMatcher... matchers) { List<WebElement> rows = getRowElementsWhere(matchers); return (!rows.isEmpty()); } public void shouldHaveRowElementsWhere(BeanMatcher... matchers) { List<WebElement> rows = getRowElementsWhere(matchers); if (rows.isEmpty()) { throw new AssertionError("Expecting a table with at least one row where: " + Arrays.deepToString(matchers)); } } public void shouldNotHaveRowElementsWhere(BeanMatcher... matchers) { List<WebElement> rows = getRowElementsWhere(matchers); if (!rows.isEmpty()) { throw new AssertionError("Expecting a table with no rows where: " + Arrays.deepToString(matchers)); } } public static HtmlTableBuilder withColumns(String... headings) { return new HtmlTableBuilder(Arrays.asList(headings)); } public static class HtmlTableBuilder { private final List<String> headings; public HtmlTableBuilder(List<String> headings) { this.headings = headings; } public List<Map<Object, String>> readRowsFrom(WebElement table) { return new HtmlTable(table, headings).getRows(); } public HtmlTable inTable(WebElement table) { return new HtmlTable(table, headings); } } private class EnoughCellsCheck { private final int minimumNumberOfCells; private EnoughCellsCheck(List<String> headings) { this.minimumNumberOfCells = headings.size(); } public boolean in(List<WebElement> cells) { return (cells.size() >= minimumNumberOfCells); } } private EnoughCellsCheck enoughCellsFor(List<String> headings) { return new EnoughCellsCheck(headings); } public List<String> getHeadings() { if (headings == null) { List<String> thHeadings = convert(headingElements(), toTextValues()); if (thHeadings.isEmpty()) { headings = convert(firstRowElements(), toTextValues()); } else { headings = thHeadings; } } return headings; } public List<WebElement> headingElements() { return tableElement.findElements(By.xpath(".//th")); } public List<WebElement> firstRowElements() { return tableElement.findElement(By.tagName("tr")).findElements(By.xpath(".//td")); } public List<WebElement> getRowElementsFor(List<String> headings) { List<WebElement> rowCandidates = tableElement.findElements(By.xpath(".//tr[td][count(td)>=" + headings.size() + "]")); rowCandidates = stripHeaderRowIfPresent(rowCandidates, headings); return rowCandidates; } public List<WebElement> getRowElements() { return getRowElementsFor(getHeadings()); } private List<WebElement> stripHeaderRowIfPresent(List<WebElement> rowCandidates, List<String> headings) { if (!rowCandidates.isEmpty()) { WebElement firstRow = rowCandidates.get(0); if (hasMatchingCellValuesIn(firstRow, headings)) { rowCandidates.remove(0); } } return rowCandidates; } private boolean hasMatchingCellValuesIn(WebElement firstRow, List<String> headings) { List<WebElement> cells = firstRow.findElements(By.xpath("./td")); for(int cellIndex = 0; cellIndex < headings.size(); cellIndex++) { if ((cells.size() < cellIndex) || (!cells.get(cellIndex).getText().equals(headings.get(cellIndex)))) { return false; } } return true; } public List<WebElement> getRowElementsWhere(BeanMatcher... matchers) { List<WebElement> rowElements = getRowElementsFor(getHeadings()); List<Integer> matchingRowIndexes = findMatchingIndexesFor(rowElements, matchers); List<WebElement> matchingElements = new ArrayList<WebElement>(); for(Integer index : matchingRowIndexes) { matchingElements.add(rowElements.get(index)); } return matchingElements; } private List<Integer> findMatchingIndexesFor(List<WebElement> rowElements, BeanMatcher[] matchers) { List<Integer> indexes = new ArrayList<Integer>(); List<String> headings = getHeadings(); int index = 0; for(WebElement row : rowElements) { List<WebElement> cells = cellsIn(row); Map<Object, String> rowData = rowDataFrom(cells, headings); if (matches(rowData, matchers)) { indexes.add(index); } index++; } return indexes; } private boolean matches(Map<Object, String> rowData, BeanMatcher[] matchers) { for(BeanMatcher matcher : matchers) { if (!matcher.matches(rowData)) { return false; } } return true; } private Map<Object,String> rowDataFrom(List<WebElement> cells, List<String> headings) { Map<Object,String> rowData = new HashMap<Object, String>(); int column = 0; for (String heading : headings) { String cell = cellValueAt(column++, cells); if (!StringUtils.isEmpty(heading)) { rowData.put(heading, cell); } rowData.put(column, cell); } return rowData; } private List<WebElement> cellsIn(WebElement row) { return row.findElements(By.xpath("./td")); } private String cellValueAt(final int column, final List<WebElement> cells) { return cells.get(column).getText(); } private Converter<WebElement, String> toTextValues() { return new Converter<WebElement, String>() { public String convert(WebElement from) { return from.getText(); } }; } public static List<Map<Object, String>> rowsFrom(final WebElement table) { return new HtmlTable(table).getRows(); } public static List<WebElement> filterRows(final WebElement table, final BeanMatcher... matchers) { return new HtmlTable(table).getRowElementsWhere(matchers); } public List<WebElement> filterRows(final BeanMatcher... matchers) { return new HtmlTable(tableElement).getRowElementsWhere(matchers); } }