/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.test.ui.po;
import java.util.List;
import org.apache.commons.lang3.StringEscapeUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Select;
/**
* Represents the actions possible on a livetable.
*
* @version $Id: 2fce4d15013ce18d4d646a5c41b1434e4a0cde55 $
* @since 3.2M3
*/
public class LiveTableElement extends BaseElement
{
private String livetableId;
public LiveTableElement(String livetableId)
{
this.livetableId = livetableId;
}
/**
* @return if the livetable has finished displaying and is ready for service
*/
public boolean isReady()
{
Object result = getDriver().executeJavascript("return Element.hasClassName('"
+ StringEscapeUtils.escapeEcmaScript(livetableId) + "-ajax-loader','hidden')");
return result instanceof Boolean ? (Boolean) result : false;
}
/**
* Wait till the livetable has finished displaying all its rows (so that we can then assert the livetable content
* without running the risk that the rows have not been updated yet).
*/
public void waitUntilReady()
{
long t1 = System.currentTimeMillis();
while ((System.currentTimeMillis() - t1 < getDriver().getTimeout() * 1000L)) {
if (isReady()) {
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Do nothing just break out
break;
}
}
throw new TimeoutException("Livetable isn't ready after the timeout has expired.");
}
public boolean hasColumn(String columnTitle)
{
List<WebElement> elements = getDriver().findElementsWithoutWaiting(
By.xpath("//th[contains(@class, 'xwiki-livetable-display-header-text') and normalize-space(.) = '"
+ columnTitle + "']"));
return elements.size() > 0;
}
public void filterColumn(String inputId, String filterValue)
{
// Make extra sure Selenium can't go quicker than the live table status by forcing it before filtering.
getDriver().executeJavascript("return $('" + StringEscapeUtils.escapeEcmaScript(livetableId)
+ "-ajax-loader').removeClassName('hidden')");
WebElement element = getDriver().findElement(By.id(inputId));
if ("select".equals(element.getTagName())) {
new Select(element).selectByVisibleText(filterValue);
} else {
element.clear();
element.sendKeys(filterValue);
}
waitUntilReady();
}
/**
* @param inputId the filter input identifier
* @return the value of the filter input for the specified column
* @see #filterColumn(String, String)
*/
public String getFilterValue(String inputId)
{
return getDriver().findElement(By.id(inputId)).getAttribute("value");
}
/**
* @param columnTitle the title of live table column
* @return the 0-based index of the specified column
*/
public int getColumnIndex(String columnTitle)
{
WebElement liveTable = getDriver().findElement(By.id(livetableId));
String escapedColumnTitle = columnTitle.replace("\\", "\\\\").replace("'", "\\'");
String columnXPath = "//thead[@class = 'xwiki-livetable-display-header']//th[normalize-space(.) = '%s']";
WebElement column = liveTable.findElement(By.xpath(String.format(columnXPath, escapedColumnTitle)));
return ((Long) ((JavascriptExecutor) getDriver()).executeScript("return arguments[0].cellIndex;", column))
.intValue();
}
/**
* Checks if there is a row that has the given value for the specified column.
*
* @param columnTitle the title of live table column
* @param columnValue the value to match rows against
* @return {@code true} if there is a row that matches the given value for the specified column, {@code false}
* otherwise
*/
public boolean hasRow(String columnTitle, String columnValue)
{
List<WebElement> elements = getRows(columnTitle);
boolean result = elements.size() > 0;
boolean match = false;
if (result) {
for (WebElement element : elements) {
match = element.getText().equals(columnValue);
if (match) {
break;
}
}
}
return result && match;
}
/**
* Checks if there are as many rows as there are passed values and check that the values match.
*
* @since 7.2M2
*/
public boolean hasExactRows(String columnTitle, List<String> columnValues)
{
List<WebElement> elements = getRows(columnTitle);
boolean result = elements.size() == columnValues.size();
if (result) {
for (int i = 0; i < elements.size(); i++) {
result = result && elements.get(i).getText().equals(columnValues.get(i));
if (!result) {
break;
}
}
}
return result;
}
private List<WebElement> getRows(String columnTitle)
{
String cellXPath = String.format(".//tr/td[position() = %s]", getColumnIndex(columnTitle) + 1);
WebElement liveTableBody = getDriver().findElement(By.id(this.livetableId + "-display"));
return liveTableBody.findElements(By.xpath(cellXPath));
}
/**
* @return the number of rows in the live table
*/
public int getRowCount()
{
WebElement liveTableBody = getDriver().findElementWithoutWaiting(By.id(this.livetableId + "-display"));
// We use XPath because we're interested only in the direct children.
return getDriver().findElementsWithoutWaiting(liveTableBody, By.xpath("tr")).size();
}
/**
* @since 5.3RC1
*/
public WebElement getRow(int rowNumber)
{
WebElement liveTableBody = getDriver().findElementWithoutWaiting(By.id(this.livetableId + "-display"));
return getDriver().findElementWithoutWaiting(liveTableBody, By.xpath("tr[" + rowNumber + "]"));
}
/**
* @since 5.3RC1
*/
public WebElement getCell(WebElement rowElement, int columnNumber)
{
return getDriver().findElementWithoutWaiting(rowElement, By.xpath("td[" + columnNumber + "]"));
}
/**
* @since 5.3RC1
*/
public ViewPage clickCell(int rowNumber, int columnNumber)
{
WebElement tdElement = getCell(getRow(rowNumber), columnNumber);
// First scroll the element into view, if needed, by moving the mouse to the top left corner of the element.
new Actions(getDriver()).moveToElement(tdElement, 0, 0).perform();
// Find the first A element and click it!
tdElement.findElement(By.tagName("a")).click();
return new ViewPage();
}
/**
* @since 3.2M3
*/
public void waitUntilRowCountGreaterThan(int minimalExpectedRowCount)
{
final By by = By.xpath("//tbody[@id = '" + this.livetableId + "-display']//tr");
getDriver().waitUntilCondition(new ExpectedCondition<Boolean>()
{
@Override
public Boolean apply(WebDriver driver)
{
// Refresh the current page since we need the livetable to fetch the JSON again
driver.navigate().refresh();
return driver.findElements(by).size() >= minimalExpectedRowCount;
}
});
}
/**
* Same as {@link #waitUntilRowCountGreaterThan(int, int)} but with a specific timeout (ie not using the default
* timeout)
*
* @since 9.1RC1
*/
// We need to decide if it's bettter to introduce this method or to globally increase the default timeout.
public void waitUntilRowCountGreaterThan(int minimalExpectedRowCount, int timeout)
{
int originalTimeout = getDriver().getTimeout();
getDriver().setTimeout(timeout);
try {
waitUntilRowCountGreaterThan(minimalExpectedRowCount);
} finally {
getDriver().setTimeout(originalTimeout);
}
}
}