/*
* JBoss, Home of Professional Open Source
* Copyright 2010-2016, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.richfaces.tests.metamer.ftest;
import static java.text.MessageFormat.format;
import static org.jboss.test.selenium.support.url.URLUtils.buildUrl;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.Graphene;
import org.jboss.arquillian.graphene.condition.element.WebElementConditionFactory;
import org.jboss.arquillian.graphene.javascript.JavaScript;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.Point;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Action;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.FindBy;
import org.richfaces.component.Positioning;
import org.richfaces.component.SwitchType;
import org.richfaces.fragment.common.AdvancedVisibleComponentIteractions;
import org.richfaces.fragment.common.Event;
import org.richfaces.fragment.common.Locations;
import org.richfaces.fragment.common.TextInputComponentImpl;
import org.richfaces.fragment.common.Utils;
import org.richfaces.fragment.common.VisibleComponent;
import org.richfaces.fragment.popupPanel.TextualRichFacesPopupPanel;
import org.richfaces.tests.configurator.unstable.UnstableTestConfigurator;
import org.richfaces.tests.metamer.Template;
import org.richfaces.tests.metamer.ftest.AbstractWebDriverTest.JSErrorStorage;
import org.richfaces.tests.metamer.ftest.attributes.AttributeEnum;
import org.richfaces.tests.metamer.ftest.extension.configurator.Configurator;
import org.richfaces.tests.metamer.ftest.extension.configurator.config.Config;
import org.richfaces.tests.metamer.ftest.extension.configurator.transformer.DataProviderTestTransformer;
import org.richfaces.tests.metamer.ftest.extension.multipleEventFiring.MultipleEventFirerer;
import org.richfaces.tests.metamer.ftest.extension.multipleEventFiring.MultipleEventFirererImpl;
import org.richfaces.tests.metamer.ftest.extension.tester.attributes.AttributeNotSetException;
import org.richfaces.tests.metamer.ftest.extension.tester.attributes.AttributesHandler;
import org.richfaces.tests.metamer.ftest.extension.tester.attributes.MultipleAttributesSetter;
import org.richfaces.tests.metamer.ftest.extension.tester.basic.TestResourcesProvider;
import org.richfaces.tests.metamer.ftest.webdriver.Attributes;
import org.richfaces.tests.metamer.ftest.webdriver.AttributesImpl;
import org.richfaces.tests.metamer.ftest.webdriver.MetamerPage;
import org.richfaces.tests.metamer.ftest.webdriver.MetamerPage.WaitRequestType;
import org.richfaces.tests.metamer.ftest.webdriver.UnsafeAttributes;
import org.richfaces.tests.metamer.ftest.webdriver.utils.MetamerJavascriptUtils;
import org.richfaces.tests.metamer.ftest.webdriver.utils.StopWatch;
import org.richfaces.tests.metamer.ftest.webdriver.utils.StringEqualsWrapper;
import org.testng.IHookCallBack;
import org.testng.ITestResult;
import org.testng.SkipException;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
public abstract class AbstractWebDriverTest extends AbstractMetamerTest {
protected static final int WAIT_TIME = 5;// s
protected static final int MINOR_WAIT_TIME = 50;// ms
protected static final int TRIES = 20;// for guardListSize and expectedReturnJS
private static final String FACES_COMPONENTS_PATH = "faces/components/";
@Drone
protected WebDriver driver;
@ArquillianResource
protected JavascriptExecutor executor;
@JavaScript
protected MetamerJavascriptUtils jsUtils;
@JavaScript
protected JSErrorStorage jsErrorStorage;
@FindBy(css = "input[id$=statusInput]")
protected TextInputComponentImpl statusInput;
@FindBy(css = "[id$=containerPopupPanel]")
protected TextualRichFacesPopupPanel popupTemplate;
@Page
private MetamerPage metamerPage;
protected DriverType driverType;
private final Boolean[] booleans = { false, true };
private Positioning positioning;// used for testJointPoint, testDirection
protected static final EnumSet<Positioning> STRICT_POSITIONING = EnumSet.of(Positioning.bottomLeft, Positioning.bottomRight, Positioning.topLeft, Positioning.topRight);
// this field is used by MetamerTestInfo to gather information about actual test method configuration
private Config currentConfiguration;
/**
* @return method should return a String representing 'component/page' (case sensitive). E.g.:
* <code>a4jActionListener/all.xhtml</code>, <code>a4jAjax/hCommandButton.xhtml</code>
*/
public abstract String getComponentTestPagePath();
@Override
public URL getTestUrl() {
return buildUrl(contextPath, FACES_COMPONENTS_PATH + getComponentTestPagePath());
}
protected void blur(WaitRequestType g) {
getMetamerPage().blur(g);
}
public enum DriverType {
FireFox(FirefoxDriver.class),
InternetExplorer(InternetExplorerDriver.class),
Chrome(ChromeDriver.class);
private final Class<?> clazz;
private DriverType(Class<?> clazz) {
this.clazz = clazz;
}
public static DriverType getCurrentType(WebDriver wd) {
for (DriverType type : values()) {
if (type.clazz.isInstance(wd)) {
return type;
}
}
throw new IllegalArgumentException("Unknown Driver");
}
}
private final Configurator c = new Configurator();
@DataProvider(name = DataProviderTestTransformer.DATAPROVIDER_NAME)
public Object[][] provide(Method m) {
return c.prepareConfigurationsForMethod(m, this);
}
@BeforeMethod(alwaysRun = true)
public void configure() {
currentConfiguration = c.configureNextStep();
}
/**
* Overriding method from Arquillian to introduce new test execution behavior
*/
@Override
public void run(final IHookCallBack callBack, final ITestResult testResult) {
super.run(UnstableTestConfigurator.getGuardedCallback(callBack), testResult);
}
@AfterMethod(alwaysRun = true)
public void unconfigure() {
if (currentConfiguration != null) {
currentConfiguration.unconfigure();
}
}
/**
* Opens the tested page. If templates is not empty nor null, it appends url parameter with templates.
*/
@BeforeMethod(alwaysRun = true, dependsOnMethods = "configure")
public void loadPage() {
if (driver == null) {
throw new SkipException("webDriver isn't initialized");
}
// delete session
driver.manage().deleteAllCookies();
// move mouse to upper left corner of window so it will not stay over tested component
moveMouseToUpperLeftCorner();
// try to load the page
for (int i = 0; i < 3; i++) {
openPageWithCurrentConfiguration();
// check page is correctlu loaded or repeat
if (checkPageIsLoadedCorrectly()) {
break;
}
}
driverType = DriverType.getCurrentType(driver);
// resize browser window to 1280x1024 or full screen
driver.manage().window().setSize(new Dimension(1920, 1080));
}
protected void openPageWithCurrentConfiguration() {
if (runInPortalEnv) {
goToTestInPortal();
} else {
driver.get(buildUrl(getTestUrl() + "?templates=" + template.toString()).toExternalForm());
}
}
/**
* During opening of a page with templates set up, one can face IndexOutOfBoundsException.
*/
private boolean checkPageIsLoadedCorrectly() {
String pageSource = driver.getPageSource();
return !pageSource.contains("java.lang.IndexOutOfBoundsException");
}
protected boolean isInPopupTemplate() {
return template.contains(Template.RICHPOPUPPANEL);
}
protected void waitUtilNoTimeoutsArePresent() {
Graphene.waitAjax().pollingEvery(200, TimeUnit.MILLISECONDS).until(new Predicate<WebDriver>() {
@Override
public boolean apply(WebDriver t) {
return Boolean.parseBoolean(String.valueOf(executeJS("return window.areNoTimeoutsPresent();")));
}
});
}
private void moveMouseToUpperLeftCorner() {
new Actions(driver).moveToElement(driver.findElement(Utils.BY_BODY), 0, 0).perform();
}
protected Attributes<BasicAttributes> getBasicAttributes() {
return getAttributes();
}
protected Attributes<MetamerAttributes> getMetamerAttributes() {
return getAttributes();
}
protected MetamerPage getMetamerPage() {
return metamerPage;
}
/**
* Sets component attribute to chosen @value. Always uses the first attribute table, unless a more specific attribute
* locator provided (e.g. @attributename="table2:onChange").
*
* @param attributeName name of the attribute (attach prefix of the attribute table if needed another attribute table than
* the first one)
* @param value value, which String representation will be set to attribute input.
*/
protected void setAttribute(String attributeName, Object value) {
getUnsafeAttributes("").set(attributeName, value);
}
/**
* Waiting method. Waits number of milis defined by @milis
*
* @param milis
*/
protected static void waiting(long milis) {
try {
Thread.sleep(milis);
} catch (InterruptedException ignored) {
}
}
protected void assertNotPresent(WebElement element, String msg) {
assertTrue(new WebElementConditionFactory(element).not().isPresent().apply(driver), msg);
}
protected void assertNotVisible(WebElement element, String msg) {
assertTrue(new WebElementConditionFactory(element).not().isVisible().apply(driver), msg);
}
protected void assertVisible(AdvancedVisibleComponentIteractions<?> o) {
assertTrue(o.advanced().isVisible());
}
protected void assertVisible(AdvancedVisibleComponentIteractions<?> o, String msg) {
assertTrue(o.advanced().isVisible(), msg);
}
protected void assertNotVisible(AdvancedVisibleComponentIteractions<?> o) {
assertFalse(o.advanced().isVisible());
}
protected void assertNotVisible(AdvancedVisibleComponentIteractions<?> o, String msg) {
assertFalse(o.advanced().isVisible(), msg);
}
protected void assertNotVisible(VisibleComponent component, String msg) {
assertFalse(component.isVisible(), msg);
}
protected void assertPresent(WebElement element, String msg) {
assertTrue(new WebElementConditionFactory(element).isPresent().apply(driver), msg);
}
protected void assertVisible(VisibleComponent component, String msg) {
assertTrue(component.isVisible(), msg);
}
protected void assertVisible(WebElement element, String msg) {
assertTrue(new WebElementConditionFactory(element).isVisible().apply(driver), msg);
}
/**
* Executes JavaScript script.
*
* @param script whole command that will be executed
* @param args
* @return may return a value or null (if expected (non-returning script) or if returning script fails)
*/
protected Object executeJS(String script, Object... args) {
return executor.executeScript(script, args);
}
/**
* Tries to execute JavaScript script for few times and expects a
*
* @expectedValue as result. Returns single trimmed String with expected value or what it found or null.
*
* @param expectedValue expected return value of javaScript
* @param script whole JavaScript that will be executed
* @param args
* @return single and trimmed string or null
*/
protected String expectedReturnJS(String script, String expectedValue, Object... args) {
String result = null;
for (int i = 0; i < TRIES; i++) {
try {
Object executedScriptResult = executeJS(script, args);
if (executedScriptResult != null) {
result = (String.valueOf(executedScriptResult)).trim();
if (result.equals(expectedValue)) {
return result;
}
}
} catch (WebDriverException ignored) {
// e.g. when retrieved property is not defined
}
waiting(100);
}
return result;
}
/**
* Helper method for testing data.
*
* @param triggeringAction
*/
protected void testData(Action triggeringAction) {
String testedValue = "RF5";
attsSetter()
.setAttribute("data").toValue(testedValue)
.setAttribute("oncomplete").toValue("data = event.data")
.asSingleAction().perform();
Graphene.guardAjax(new ActionWrapper(triggeringAction)).perform();
assertEquals(expectedReturnJS("return window.data;", testedValue), testedValue);
}
/**
* Test attribute @limitRender. Sets @render attribute to '@this renderChecker' and @limitRender to 'true' and performs an
* ajax-guarded action.
*/
protected void testLimitRender(Action triggeringAction) {
attsSetter()
.setAttribute("limitRender").toValue(true)
.setAttribute("render").toValue("@this renderChecker")
.asSingleAction().perform();
String renderCheckerText = metamerPage.getRenderCheckerOutputElement().getText();
String requestTime = metamerPage.getRequestTimeElement().getText();
Graphene.guardAjax(new ActionWrapper(triggeringAction)).perform();
Graphene.waitGui().until().element(metamerPage.getRenderCheckerOutputElement()).text().not()
.equalTo(renderCheckerText);
Graphene.waitGui().until().element(metamerPage.getRequestTimeElement()).text()
.equalTo(requestTime);
}
/**
* Same as testLimitRender, but tries to set @switchType or @mode to 'ajax'
*/
protected void testLimitRenderWithSwitchTypeOrMode(Action triggeringAction) {
setModeOrSwitchTypeToAjax();
testLimitRender(triggeringAction);
}
/**
* Test attribute @render. Sets mentioned attribute to '@this renderChecker' and performs an ajax-guarded action. * .
*/
protected void testRender(Action triggeringAction) {
getUnsafeAttributes().set("render", "@this renderChecker");
String renderCheckerText = metamerPage.getRenderCheckerOutputElement().getText();
String requestTime = metamerPage.getRequestTimeElement().getText();
Graphene.guardAjax(new ActionWrapper(triggeringAction)).perform();
Graphene.waitGui().until().element(metamerPage.getRenderCheckerOutputElement()).text().not()
.equalTo(renderCheckerText);
Graphene.waitGui().until().element(metamerPage.getRequestTimeElement()).text().not()
.equalTo(requestTime);
}
/**
* Same as testRender, but tries to set @switchType or @mode to 'ajax'
*/
protected void testRenderWithSwitchTypeOrMode(Action triggeringAction) {
setModeOrSwitchTypeToAjax();
testRender(triggeringAction);
}
private void setModeOrSwitchTypeToAjax() {
UnsafeAttributes attributes = getUnsafeAttributes("");
try {
attributes.set("mode", SwitchType.ajax);
return;
} catch (RuntimeException ignored) {
}
try {
attributes.set("switchType", SwitchType.ajax);
return;
} catch (RuntimeException ignored) {
}
fail("Neither of @mode nor @switchType was found.");
}
/**
* Testing of HTMLAttribute (e.g. type).
*
* E.g. testHTMLAttribute(page.link, mediaOutputAttributes, MediaOutputAttributes.type, "text/html");
*
* @param element WebElement which will be checked for containment of tested attribute
* @param attributes attributes instance which will be used for setting attribute
* @param testedAttribute attribute which will be tested
* @param value tested value of attribute
*/
protected <T extends AttributeEnum> void testHTMLAttribute(WebElement element, Attributes<T> attributes, T testedAttribute,
String value) {
attributes.set(testedAttribute, value);
String attString = Attribute2StringDecoder.decodeAttribute(testedAttribute);
String valueOnPage = element.getAttribute(attString);
if (new StringEqualsWrapper(value).equalsToSomeOfThis(null, "", "null")) {
if (new StringEqualsWrapper(valueOnPage).notEqualsToSomeOfThis(null, "", "null")) {
fail("Attribute " + testedAttribute.toString() + " does not work properly, Value of attribute on page: '"
+ valueOnPage + "', expected value '" + value + "'.");
}
} else if (!valueOnPage.contains(value)) {// Attribute has not been set correctly
fail("Attribute " + testedAttribute.toString() + " does not work properly, Value of attribute on page: '"
+ valueOnPage + "', expected value '" + value + "'.");
}
}
/**
* Testing of HTMLAttribute (e.g. type).
*
* E.g. testHTMLAttribute(page.link, mediaOutputAttributes, MediaOutputAttributes.type, "text/html");
*
* @param element FutureTarget of WebElement which will be checked for containment of tested attribute
* @param attributes attributes instance which will be used for setting attribute
* @param testedAttribute attribute which will be tested
* @param value tested value of attribute
* @param actionAfterSettingOfAttribute action which will be performed after setting the attribute(e.g. open popup), if it
* is null then it is skipped
*/
protected <T extends AttributeEnum> void testHTMLAttribute(FutureTarget<WebElement> element, Attributes<T> attributes,
T testedAttribute, String value, Action actionAfterSettingOfAttribute) {
attributes.set(testedAttribute, value);
if (actionAfterSettingOfAttribute != null) {
actionAfterSettingOfAttribute.perform();
}
String attString = Attribute2StringDecoder.decodeAttribute(testedAttribute);
String valueOnPage = element.getTarget().getAttribute(attString);
if (new StringEqualsWrapper(value).equalsToSomeOfThis(null, "", "null")) {
if (new StringEqualsWrapper(valueOnPage).notEqualsToSomeOfThis(null, "", "null")) {
fail("Attribute " + testedAttribute.toString() + " does not work properly, Value of attribute on page: '"
+ valueOnPage + "', expected value '" + value + "'.");
}
} else if (!valueOnPage.contains(value)) {// Attribute has not been set correctly
fail("Attribute " + testedAttribute.toString() + " does not work properly, Value of attribute on page: '"
+ valueOnPage + "', expected value '" + value + "'.");
}
}
/**
* Testing of HTMLAttribute (e.g. type). Expects that if an attribute is set to @value, then the value will be set to
*
* @anotherValue (e.g. null -> submit for a4j:commandButton)
*
* E.g. testHTMLAttribute(page.link, mediaOutputAttributes, MediaOutputAttributes.type, "text/html");
*
* @param element WebElement which will be checked for containment of tested attribute
* @param attributes attributes instance which will be used for setting attribute
* @param testedAttribute attribute which will be tested
* @param value tested value of attribute
* @param anotherValue value that will replace @value
*/
protected <T extends AttributeEnum> void testHTMLAttribute(WebElement element, Attributes<T> attributes, T testedAttribute,
String value, String anotherValue) {
attributes.set(testedAttribute, value);
String attString = Attribute2StringDecoder.decodeAttribute(testedAttribute);
String valueOnPage = element.getAttribute(attString);
if (new StringEqualsWrapper(value).equalsToSomeOfThis(null, "", "null")) {
if (new StringEqualsWrapper(anotherValue).isNotSimilarToSomeOfThis(valueOnPage)) {
fail("Attribute " + testedAttribute.toString() + " does not work properly, Value of attribute on page: '"
+ valueOnPage + "', expected value '" + anotherValue + "'.");
}
} else if (new StringEqualsWrapper(anotherValue).isNotSimilarToSomeOfThis(value)) {// Attribute has not been set
// correctly
fail("Attribute " + testedAttribute.toString() + " does not work properly, Value of attribute on page: '"
+ valueOnPage + "', expected value '" + anotherValue + "'.");
}
}
/**
* Testing of HTMLAttribute. The tested value is RichFaces 4.
*
* @param element WebElement which will be checked for containment of tested attribute
* @param attributes attributes instance which will be used for setting attribute
* @param testedAttribute attribute which will be tested
*/
protected <T extends AttributeEnum> void testHTMLAttribute(WebElement element, Attributes<T> attributes, T testedAttribute) {
testHTMLAttribute(element, attributes, testedAttribute, "RichFaces 4");
}
/**
* Tests lang attribute of chosen component in Metamer. Page must contain an input for this component's attribute.
*
* @param element WebElement representing component.
*/
protected void testLang(WebElement element) {
final String TESTVALUE = "cz";
String attLang;
// set lang to TESTVALUE
getBasicAttributes().set(BasicAttributes.lang, TESTVALUE);
// get attribute lang of element
String lang1 = element.getAttribute("xml:lang");
String lang2 = element.getAttribute("lang");
attLang = (lang1 == null || lang1.isEmpty() ? lang2 : lang1);
assertEquals(attLang, TESTVALUE, "Attribute xml:lang should be present.");
}
/**
* Helper method for testing of delays (showDelay, hideDelay). Runs the @actionWithDelay 4 times and measure time spent in
* it. Then count a median from these 4 values and asserts it to the @expectedDelay with 50% tolerance.
*
* @param actionBefore action before the measured action. Can be used for e.g. close/open menu. Can be null.
* @param actionWithDelay the measured action. Can be e.g. open/close menu.
* @param attributeName name of the measured attribute (e.g. hideDelay, showDelay).
* @param expectedDelayInMillis expected delay spent in @actionWithDelay and also a value that will be set in attribute with
* name @attributeName
*/
protected void testDelay(final Action actionBefore, final Action actionWithDelay, String attributeName, long expectedDelayInMillis) {
getUnsafeAttributes("").set(attributeName, expectedDelayInMillis);
double tolerance = expectedDelayInMillis == 0 ? 500 : expectedDelayInMillis * 0.5;
int cycles = 4;
List<Long> delays = Lists.newArrayList();
for (int i = 0; i < cycles; i++) {
//This is debug output, to determine in which cycle test falls.
System.out.println(format("Tested delay: {0}, cycle: {1}", expectedDelayInMillis, i));
if (actionBefore != null) {
actionBefore.perform();
}
delays.add(StopWatch.watchTimeSpentInAction(actionWithDelay).inMillis().longValue());
}
Number median = countMedian(delays);
assertEquals(median.doubleValue(), expectedDelayInMillis, tolerance, "The delay is not in tolerance. Median of delays was " + median);
}
/**
* A helper method for testing attribute "dir". It tries null, ltr and rtl.
*
* @param element WebElement reference of tested element
*/
protected void testDir(WebElement element) {
testHTMLAttribute(element, getBasicAttributes(), BasicAttributes.dir, "null");
testHTMLAttribute(element, getBasicAttributes(), BasicAttributes.dir, "ltr");
testHTMLAttribute(element, getBasicAttributes(), BasicAttributes.dir, "rtl");
}
/**
* Use with <code>@UseWithField(field = "positioning",valuesFrom = FROM_ENUM, value = "")</code>.
*
* @param showAction action which will show the tested menu and will return it as a WebElement.
*/
protected void testDirection(ShowElementAndReturnAction showAction) {
testPositioning(showAction, 0, 0);
}
/**
* Use with <code>@UseWithField(field = "positioning",valuesFrom = FROM_ENUM, value = "")</code>.
*
* @param maxOffSetX width of the menu item, input, etc. from which will be the menu displayed
* @param maxOffsetY height of the menu item, input, etc. from which will be the menu displayed
* @param showAction action which will show the tested menu and will return it as a WebElement.
*/
protected void testJointPoint(int maxOffSetX, int maxOffsetY, ShowElementAndReturnAction showAction) {
testPositioning(showAction, maxOffSetX, maxOffsetY);
}
private void testPositioning(ShowElementAndReturnAction showAction, int maxOffSetX, int maxOffsetY) {
int tolerance = 10;
boolean isDirectionTest = (maxOffsetY == 0 && maxOffSetX == 0);
UnsafeAttributes atts = getUnsafeAttributes("");
// determine tested attribute
String testedAtt = isDirectionTest ? "direction" : "jointPoint";
// set reference values
if (atts.hasAttribute("jointPoint")) {
attsSetter()
.setAttribute("direction").toValue(Positioning.bottomRight)
.setAttribute("jointPoint").toValue(Positioning.bottomRight)
.asSingleAction().perform();
} else {// no @jointPoint attribute, set direction only
atts.set("direction", Positioning.bottomRight);
}
// get reference locations
Locations locationsBottomRight = Utils.getLocations(showAction.perform());
// setup tested attribute
atts.set(testedAtt, positioning);
if (atts.get(testedAtt).equals(Positioning.bottomRight.toString())) {
// reload the page to dismiss the popup
openPageWithCurrentConfiguration();
}
// get new locations, with tested value of @direction/@jointPoint
Locations locationsAfterReposition = Utils.getLocations(showAction.perform());
int widthChange = maxOffSetX;
int heightChange = maxOffsetY;
if (isDirectionTest) {
widthChange = locationsBottomRight.getWidth();
heightChange = locationsBottomRight.getHeight();
}
// check
if (STRICT_POSITIONING.contains(positioning)) {
Utils.tolerantAssertLocationsEquals(getLocationsOfBottomRightAfterPositioningChanges(locationsBottomRight, positioning, widthChange, heightChange), locationsAfterReposition, tolerance, tolerance, "");
} else {// some '*auto*' option
// cycle through all strict directions, one must be the same as the '*auto*',
// which one it will be depends on browser/screen resolution and actual position
for (Positioning pos : STRICT_POSITIONING) {
try {
Utils.tolerantAssertLocationsEquals(getLocationsOfBottomRightAfterPositioningChanges(locationsBottomRight, pos, widthChange, heightChange), locationsAfterReposition, tolerance, tolerance, "");
return;
} catch (AssertionError ignored) {
}
}
fail("No position was close enough for direction " + positioning.toString());
}
}
private Locations getLocationsOfBottomRightAfterPositioningChanges(Locations locations, Positioning jointPoint, int width, int height) {
switch (jointPoint) {
case topLeft:
return locations.moveAllBy(-width, -height);
case topRight:
return locations.moveAllBy(0, -height);
case bottomLeft:
return locations.moveAllBy(-width, 0);
case bottomRight:
return locations;
default:
throw new UnsupportedOperationException("Not supported positioning: " + jointPoint);
}
}
/**
* @param showAction action which will show the tested menu and will return it as a WebElement.
*/
protected void testHorizontalOffset(ShowElementAndReturnAction showAction) {
testOffset(false, showAction);
}
/**
* @param showAction action which will show the tested menu and will return it as a WebElement.
*/
protected void testVerticalOffset(ShowElementAndReturnAction showAction) {
testOffset(true, showAction);
}
private void testOffset(boolean isVerticalOffset, ShowElementAndReturnAction showAction) {
int tolerance = 10;
int testedOffset = 50;
try {
getUnsafeAttributes("").set("direction", Positioning.bottomRight);
} catch (AttributeNotSetException ignored) {
}
try {
getUnsafeAttributes("").set("jointPoint", Positioning.bottomRight);
} catch (AttributeNotSetException ignored) {
}
Locations locationsBefore = Utils.getLocations(showAction.perform());
getUnsafeAttributes("").set((isVerticalOffset ? "verticalOffset" : "horizontalOffset"), testedOffset);
Locations locationsAfter = Utils.getLocations(showAction.perform());
Utils.tolerantAssertLocationsEquals(locationsAfter, locationsBefore.moveAllBy((isVerticalOffset ? 0 : testedOffset), (isVerticalOffset ? testedOffset : 0)), tolerance, tolerance, "");
}
/**
* A helper method for testing JavaScripts events. It sets "metamerEvents += "testedAttribute" to the input field of the
* tested attribute and fires the event @event using jQuery on the given element @element. Then it checks if the event was
* fired. This method should only be used for attributes consistent with DOM events (e.g. (on)click, (on)change...).
*
* @param element WebElement on which will be the event triggered
* @param attributes attributes instance which will be used for setting attribute
* @param testedAttribute attribute which will be tested
*/
protected <T extends AttributeEnum> void testFireEventWithJS(WebElement element, Attributes<T> attributes, final T testedAttribute) {
attributes.set(testedAttribute, "metamerEvents += \"" + testedAttribute.toString() + " \"");
executeJS("metamerEvents = \"\";");
Event e = new Event(testedAttribute.toString().substring(2));// remove prefix "on"
fireEvent(element, e);
Graphene.waitGui().withMessage("Event " + e + " does not work.").until(new Predicate<WebDriver>() {
@Override
public boolean apply(WebDriver wd) {
String metamerEvents = executor.executeScript("return metamerEvents").toString().trim();
return testedAttribute.toString().equals(metamerEvents);
}
});
}
/**
* A helper method for testing JavaScripts events. It sets "metamerEvents += "testedAttribute" to the input field of the
* tested attribute and fires the event @event using jQuery on the element @element. Then it checks if the event was fired.
*
* @event using jQuery on the element
* @element. Then it checks if the event was fired.
*
* @see testFireEventWithJS(WebElement element, Attributes<T> attributes, T testedAttribute)
* @param element WebElement on which will be the event triggered
* @param event event wich will be triggered
* @param attributes attributes instance which will be used for setting attribute
* @param testedAttribute attribute which will be tested
*/
protected <T extends AttributeEnum> void testFireEventWithJS(WebElement element, Event event, Attributes<T> attributes,
T testedAttribute) {
attributes.set(testedAttribute, "metamerEvents += \"" + testedAttribute.toString() + " \"");
executeJS("metamerEvents = \"\";");
fireEvent(element, event);
String returnedString = expectedReturnJS("return metamerEvents", testedAttribute.toString());
assertEquals(returnedString, testedAttribute.toString(), "Event " + event + " does not work.");
}
/**
* A helper method for testing events. It sets "metamerEvents += "@testedAttribute" to the input field and fires the event
* using Actions. Then it checks if the event was fired.
*
* @param attributes attributes instance which will be used for setting attribute
* @param testedAttribute attribute which will be tested
* @param eventFiringAction selenium action which leads to launch the tested event,
*/
protected <T extends AttributeEnum> void testFireEvent(Attributes<T> attributes, T testedAttribute, Action eventFiringAction) {
attributes.set(testedAttribute, "metamerEvents += \"" + testedAttribute.toString() + " \"");
executeJS("metamerEvents = \"\";");
try {
eventFiringAction.perform();
String returnedString = expectedReturnJS("return metamerEvents", testedAttribute.toString());
assertEquals(returnedString, testedAttribute.toString(), "Event " + testedAttribute.toString() + " does not work.");
} finally {
if (testedAttribute.toString().toLowerCase().endsWith("mousedown")) {
tryToReleaseMouseButton();
}
}
}
/**
* A helper method for testing JavaScripts events.
*
* @param event JavaScript event to be tested
* @param element element on which will be the event triggered
*/
protected void testFireEvent(Event event, WebElement element) {
testFireEvent(event, element, event.getEventName());
}
/**
* A helper method for testing JavaScripts events.
*
* @param event JavaScript event to be tested
* @param element FutureTarget of WebElement on which will be the event triggered
* @param actionBeforeFiringTheEvent action which will be performed before firing the event
*/
protected void testFireEvent(final Event event, final FutureTarget<WebElement> element,
final Action actionBeforeFiringTheEvent) {
testFireEvent(event.getEventName(), new Action() {
@Override
public void perform() {
if (actionBeforeFiringTheEvent != null) {
actionBeforeFiringTheEvent.perform();
}
fireEvent(element.getTarget(), event);
}
});
}
/**
* A helper method for testing JavaScripts events.
*
* @param event JavaScript event to be tested
* @param element element on which will be the event triggered (must be present before test, or a proxy)
* @param attributeName name of the attribute that should be set
*/
protected void testFireEvent(final Event event, final WebElement element, String attributeName) {
testFireEvent(attributeName, new Action() {
@Override
public void perform() {
fireEvent(element, event);
}
});
}
/**
* A helper method for testing events.
*
* @param attributeName name of the attribute that should be set (i.e. inputselect, onselect ; can be without the prefix
* 'on')
* @param eventFiringAction action which will be performed to trigger the event
*/
protected void testFireEvent(String attributeName, Action eventFiringAction) {
setAttribute((attributeName.startsWith("on") ? attributeName : "on" + attributeName), "metamerEvents += \""
+ attributeName + " \"");
// clear/init events
executeJS("metamerEvents = \"\";");
// trigger event
try {
eventFiringAction.perform();
// check
assertEquals(expectedReturnJS("return metamerEvents", attributeName), attributeName, "Attribute " + attributeName
+ " does not work.");
} finally {
if (attributeName.toLowerCase().endsWith("mousedown")) {
tryToReleaseMouseButton();
}
}
}
private void tryToReleaseMouseButton() {
try {
new Actions(driver).release().perform();
} catch (WebDriverException ignored) {
}
}
/**
* Method for firing JavaScript events on given element using jQuery.
*
* @param element
* @param event
*/
protected void fireEvent(WebElement element, Event event) {
Utils.triggerJQ(executor, event.getEventName(), element);
}
/**
* Helper method for testing label's text changing. At first it sets "RichFaces 4" to the <code>testedAttribute</code>
* input, then fires <code>labelChangeAction</code>(if some), then waits for the visibility of <code>element</code> and
* finally checks if the label (<code>getText()</code> method) of <code>element</code> was changed as expected.
*
* @param element element which <code>getText()</code> method will be used for checking of label text
* @param attributes attributes instance which will be used for setting attribute
* @param testedAttribute attribute which will be tested
* @param labelChangeAction action which will change the label (if no action needed use <code>null</code> or empty Action)
*/
protected <T extends AttributeEnum> void testLabelChanges(WebElement element, Attributes<T> attributes, T testedAttribute,
Action labelChangeAction) {
testLabelChanges(FutureWebElement.of(element), attributes, testedAttribute, labelChangeAction);
}
protected <T extends AttributeEnum> void testLabelChanges(FutureTarget<WebElement> futureTarget, Attributes<T> attributes,
T testedAttribute, Action labelChangeAction) {
String rf = "RichFaces 4";
attributes.set(testedAttribute, rf);
if (labelChangeAction != null) {
labelChangeAction.perform();
}
Graphene.waitModel().until().element(futureTarget.getTarget()).is().visible();
Graphene.waitModel().until(testedAttribute + " does not work, label has not changed.")
.element(futureTarget.getTarget()).text().equalTo(rf);
}
protected <T extends AttributeEnum> void testLabelChanges(String attributeName, FutureTarget<WebElement> futureTarget,
Action labelChangeAction) {
String rf = "RichFaces 4";
setAttribute(attributeName, rf);
if (labelChangeAction != null) {
labelChangeAction.perform();
}
Graphene.waitModel().until().element(futureTarget.getTarget()).is().visible();
Graphene.waitModel().until(attributeName + " does not work, label has not changed.").element(futureTarget.getTarget())
.text().equalTo(rf);
}
/**
* Helper method for testing of attribute 'status'. At first it sets @status to "statusChecker", then saves Metamer's
* 'statusCheckerOutput' time, then fires <code>statusChangingAction</code> (if not null), and finally checks if Metamer's
* 'statusCheckerOutput' time was changed before Graphene.waitModel() interval expires.
*
* @param statusChangingAction action that will change the status. Can be null.
*/
protected void testStatus(Action statusChangingAction) {
String checker = "statusChecker";
// set attribute
getUnsafeAttributes("").set("status", checker);
String statusCheckerTimeBefore = metamerPage.getStatusCheckerOutputElement().getText();
if (statusChangingAction != null) {
Graphene.guardAjax(new ActionWrapper(statusChangingAction)).perform();
}
Graphene.waitModel().until().element(metamerPage.getStatusCheckerOutputElement()).text().not()
.equalTo(statusCheckerTimeBefore);
}
/**
* A helper method for testing attribute "style" or similar. It sets "background-color: yellow; font-size: 1.5em;" to the
* input field and checks that it was changed on the page.
*
* @param element WebElement reference of tested element
* @param attribute name of the attribute that will be set (e.g. style, headerStyle, itemContentStyle)
*/
protected void testStyle(final WebElement element, BasicAttributes attribute) {
final String value = "background-color: yellow;";
testHTMLAttribute(element, getBasicAttributes(), attribute, value);
}
/**
* A helper method for testing attribute "style". It sets "background-color: yellow; font-size: 1.5em;" to the input field
* and checks that it was changed on the page.
*
* @param element WebElement reference of tested element
*/
protected void testStyle(final WebElement element) {
testStyle(element, BasicAttributes.style);
}
/**
* A helper method for testing attribute "class" or similar. It sets "metamer-ftest-class" to the input field and checks
* that it was changed on the page.
*
* @param element WebElement reference of tested element
* @param attribute name of the attribute that will be set (e.g. styleClass, headerClass, itemContentClass)
*/
protected void testStyleClass(WebElement element, BasicAttributes attribute) {
final String styleClass = "metamer-ftest-class";
testHTMLAttribute(element, getBasicAttributes(), attribute, styleClass);
}
/**
* A helper method for testing attribute "class". It sets "metamer-ftest-class" to the input field and checks that it was
* changed on the page.
*
* @param element locator of tested element
*/
protected void testStyleClass(WebElement element) {
testStyleClass(element, BasicAttributes.styleClass);
}
/**
* A helper method for testing attribute "title".
*
* @param element WebElement reference of tested element
*/
protected void testTitle(WebElement element) {
final String testTitle = "RichFaces 4";
testHTMLAttribute(element, getBasicAttributes(), BasicAttributes.title, testTitle);
}
/**
* Helper method for testing table's style class attributes (e.g. cellClass, columnFooterClass)
*
* @param attributeName
* @param elementClassName
* @param expectedCount
*/
protected void testTableStyleClass(String attributeName, String elementClassName, int expectedCount) {
String klass = "metamer-ftest-class";
setAttribute(attributeName, klass);
List<WebElement> elems = driver.findElements(By.className(elementClassName));
assertTrue(expectedCount > 0);
assertEquals(elems.size(), expectedCount);
for (WebElement elem : elems) {
assertTrue(elem.getAttribute("class").contains(klass));
}
}
/**
* Checks that there is no 404 error in browser's console.
*
* @param beforeCheckAction action berformed before checking. Can be null (no action will be performed).
*/
protected void checkNoResourceErrorPresent(Action beforeCheckAction) {
// perform action
if (beforeCheckAction != null) {
beforeCheckAction.perform();
}
final String errMsg = "failed to load resource";
for (String msg : jsErrorStorage.getMessages()) {
assertFalse(msg.contains(errMsg), "There should be no resource error message in browser's console. Had: <" + msg + ">.");
}
}
/**
* Tries to check and wait for correct size (@size) of list. Depends on list of WebElements decorated with
* StaleReferenceAwareFieldDecorator.
*
* @param list input list
* @param size expected size of list
* @return list with or without expected size
*/
protected List<WebElement> guardListSize(List<WebElement> list, int size) {
boolean lastCheckWithModifications;
int checkedSize = list.size();
for (int i = 0; i < TRIES; i++) {
if (checkedSize < list.size()) {
checkedSize = list.size();
lastCheckWithModifications = true;
} else {
lastCheckWithModifications = false;
}
if (checkedSize >= size && !lastCheckWithModifications) {
// last check
waiting(MINOR_WAIT_TIME);
list.size();
return list;
}
waiting(MINOR_WAIT_TIME);
}
return list;
}
protected <T extends AttributeEnum> Attributes<T> getAttributes(String attributesTableId) {
return AttributesImpl.<T>getAttributesFor(testResourcesProvider, attributesTableId);
}
protected <T extends AttributeEnum> Attributes<T> getAttributes() {
return getAttributes("");
}
protected UnsafeAttributes getUnsafeAttributes(String attributesTableId) {
return new AttributesImpl<AttributeEnum>(testResourcesProvider, attributesTableId);
}
protected UnsafeAttributes getUnsafeAttributes() {
return getUnsafeAttributes("");
}
/**
* Method used to run selenium test in portal environment.
*/
private void goToTestInPortal() {
driver.get(String.format("%s://%s:%s/%s", contextPath.getProtocol(), contextPath.getHost(), contextPath.getPort(),
"portal/classic/metamer"));
try {
driver.findElement(By.cssSelector("a[id$='controlsForm:goHomeLink']")).click();
// JSF form works only on home page
} catch (NoSuchElementException ex) {
}
JavascriptExecutor js = (JavascriptExecutor) driver;
String setTextQuery = "document.querySelector(\"input[id$='linksForm:%s']\").value = '%s';";
String testUrl = getTestUrl().toExternalForm().substring(getTestUrl().toExternalForm().indexOf("faces"));
js.executeScript(String.format(setTextQuery, "linkToTest", testUrl));
js.executeScript(String.format(setTextQuery, "template", template.toString()));
js.executeScript("document.querySelector(\"a[id$='linksForm:redirectToPortlet']\").click()");
}
public void testRequestEventsBefore(String... events) {
String testedEventTrueName;
MultipleAttributesSetter attsSetter = attsSetter();
for (String event : events) {
testedEventTrueName = event;
if (!testedEventTrueName.startsWith("on")) {
testedEventTrueName = format("on{0}", testedEventTrueName);
}
attsSetter
.setAttribute(testedEventTrueName)
.toValue(format("sessionStorage.setItem(\"metamerEvents\", sessionStorage.getItem(\"metamerEvents\") + \"{0} \")", event));
}
attsSetter.asSingleAction().perform();
cleanMetamerEventsVariable();
}
public void testRequestEventsAfter(final String... events) {
Graphene.waitModel().until(new Predicate<WebDriver>() {
private String actualEvents;
private int lastNumberOfEvents;
@Override
public boolean apply(WebDriver arg0) {
actualEvents = ((String) executeJS("return sessionStorage.getItem(\"metamerEvents\")"));
lastNumberOfEvents = (StringUtils.isBlank(actualEvents) ? 0 : actualEvents.split(" ").length);
return lastNumberOfEvents == events.length;
}
@Override
public String toString() {
return format("number of events is equal to {0}, found {1}. Actual events: {2}", events.length,
lastNumberOfEvents, actualEvents);
}
});
String[] actualEvents = ((String) executeJS("return sessionStorage.getItem(\"metamerEvents\")")).split(" ");
assertEquals(
actualEvents,
events,
String.format("The events (%s) don't came in right order (%s)", Arrays.deepToString(actualEvents),
Arrays.deepToString(events)));
}
public MultipleEventFirerer getMultipleEventsFirerer() {
return new MultipleEventFirererImpl(executor);
}
public void cleanMetamerEventsVariable() {
// since metamerEvents variable stored on session too, make sure that cleaned both of them
executeJS("window.metamerEvents = \"\";");
executeJS("sessionStorage.setItem('metamerEvents',\"\")");
}
public static <T extends Number & Comparable<T>> Number countMedian(List<T> values) {
assertTrue(values.size() > 0);
if (values.size() == 1) {
return values.get(0);
}
final List<T> copy = Lists.newArrayList(values);
Collections.sort(copy);
int middleIndex = (copy.size() - 1) / 2;
double result = copy.get(middleIndex).doubleValue();
if (copy.size() % 2 == 0) {
result = (result + copy.get(middleIndex + 1).doubleValue()) / 2.0;
}
final Double median = Double.valueOf(result);
return new Number() {
private static final long serialVersionUID = 1L;
@Override
public int intValue() {
return median.intValue();
}
@Override
public long longValue() {
return median.longValue();
}
@Override
public float floatValue() {
return median.floatValue();
}
@Override
public double doubleValue() {
return median.doubleValue();
}
@Override
public String toString() {
return median.doubleValue() + " from values(sorted) " + copy.toString() + '.';
}
};
}
/**
* Decoder for Attributes. Converts given Attribute to String. If Attribute ends with 'class' or 'style', then it returns
* the correct one, when the attribute does not end with none of those, then it returns toString() method of attribute
*/
public static class Attribute2StringDecoder {
private static final String[] ATTRIBUTES = { "class", "classes", "style" };
public static <T extends AttributeEnum> String decodeAttribute(T testedAttribute) {
String testedAtt = testedAttribute.toString();
if (testedAtt.length() > 6) {
// get the ending
String tmp = testedAtt.toLowerCase();
for (String string : ATTRIBUTES) {
if (tmp.lastIndexOf(string) > 0) {// contains an attribute to decode
if (string.equalsIgnoreCase(ATTRIBUTES[0]) || string.equalsIgnoreCase(ATTRIBUTES[1])) {
return ATTRIBUTES[0];
} else if (string.equalsIgnoreCase(ATTRIBUTES[2])) {
return ATTRIBUTES[2];
} else {
throw new RuntimeException("Cannot decode attribute " + testedAtt);
}
}
}
}
return testedAtt;
}
}
/**
* Abstract ReloadTester for testing component's state after reloading the page
*
* @param <T> the type of input values which will be set, sent and then verified
*/
public abstract class ReloadTester<T> {
public abstract void doRequest(T inputValue);
public abstract void verifyResponse(T inputValue);
public abstract T[] getInputValues();
public void testRerenderAll() {
for (T inputValue : getInputValues()) {
doRequest(inputValue);
verifyResponse(inputValue);
getMetamerPage().rerenderAll();
verifyResponse(inputValue);
}
}
public void testFullPageRefresh() {
for (T inputValue : getInputValues()) {
doRequest(inputValue);
verifyResponse(inputValue);
getMetamerPage().fullPageRefresh();
verifyResponse(inputValue);
}
}
}
protected interface ShowElementAndReturnAction {
WebElement perform();
}
public interface FutureTarget<T> {
T getTarget();
}
/**
* Wrapper for anonymous actions, so it can be guarded by graphene.
*/
public static class ActionWrapper implements Action {
private final Action a;
public ActionWrapper(Action a) {
this.a = a;
}
@Override
public void perform() {
a.perform();
}
}
public static final class FutureWebElement {
private FutureWebElement() {
}
public static FutureTarget<WebElement> of(final WebElement element) {
return new FutureTarget<WebElement>() {
@Override
public WebElement getTarget() {
return element;
}
};
}
public static FutureTarget<WebElement> of(final By by, final SearchContext context) {
return new FutureTarget<WebElement>() {
@Override
public WebElement getTarget() {
return context.findElement(by);
}
};
}
}
/**
* Helper class for testing of popup menu's hide/show delays. Executes the measuring in browser using JavaScript, JS API of
* the component and attributes 'onhide' and 'onshow' of the component.
*/
protected class MenuDelayTester {
private static final String ATTRIBUTE_TIME_NAME = "delayTime";
private static final String ATTRIBUTE_TRIGGERED_NAME = "delayAttributeTriggered";
private static final String HIDE_DELAY = "hideDelay";
private static final String ON_HIDE = "onhide";
private static final String ON_SHOW = "onshow";
private static final String SHOW_DELAY = "showDelay";
private final String GET_TIME_SCRIPT = format("return window.{0};", ATTRIBUTE_TIME_NAME);
private final String MEASURING_SCRIPT_TEMPLATE = String.format(
"window.%s = false;"
+ "var event = jQuery.Event(\"click\");"// event type does not matter, but has to be non-empty
+ "event.pageX = {0};"
+ "event.pageY = {1};"
+ "RichFaces.component(\"{2}\").{3}(event);"
+ "window.%s = new Date().getTime();",
ATTRIBUTE_TRIGGERED_NAME,
ATTRIBUTE_TIME_NAME);
private final String PREPARATION_SCRIPT = format("window.{0}=new Date().getTime() - window.{0}; window.{1}=true;",
ATTRIBUTE_TIME_NAME, ATTRIBUTE_TRIGGERED_NAME);
public MenuDelayTester() {
}
private String getMeasuringScript(WebElement rootElement, boolean isHideDelay, Event[] triggerEvents) {
Point location = rootElement.getLocation();
Locations locations = Utils.getLocations(rootElement);
int x = location.x + locations.getWidth() / 2;
int y = location.y + locations.getHeight() / 2;
String id = rootElement.getAttribute("id");
String first = isHideDelay ? "show" : "hide";
StringBuilder sb = new StringBuilder(MEASURING_SCRIPT_TEMPLATE);
for (Event e : triggerEvents) {
sb.append("event = jQuery.Event(\"").append(e)
.append("\");event.pageX={0};event.pageY = {1};jQuery(arguments[0]).trigger(event);");
}
return format(sb.toString(), x, y, id, first);
}
private void testDelay(boolean isHideDelay, final WebElement menuRootElement, final long expectedDelayInMillis, Event[] triggerEvent, WebElement triggerEventOnElement) {
double tolerance = expectedDelayInMillis == 0 ? 500 : expectedDelayInMillis * 0.5;
int cycles = 4;
attsSetter()
.setAttribute(isHideDelay ? HIDE_DELAY : SHOW_DELAY).toValue(expectedDelayInMillis)// set tested attribute
.setAttribute(isHideDelay ? SHOW_DELAY : HIDE_DELAY).toValue(0)// reset not tested attribute
.setAttribute(isHideDelay ? ON_HIDE : ON_SHOW).toValue(PREPARATION_SCRIPT)
.asSingleAction().perform();
List<Long> delays = new ArrayList<Long>(cycles);
String measuringScript = getMeasuringScript(menuRootElement, isHideDelay, triggerEvent);
for (int i = 1; i <= cycles; i++) {
//This is debug output, to determine in which cycle test could fall.
System.out.println(format("Tested delay: {0} [ms], cycle: {1}", expectedDelayInMillis, i));
executor.executeScript(measuringScript, triggerEventOnElement);
Graphene.waitGui()
.withTimeout(expectedDelayInMillis * 2, TimeUnit.MILLISECONDS)
.until(new EventTriggeredPredicate(ATTRIBUTE_TRIGGERED_NAME));
delays.add(Long.valueOf(executor.executeScript(GET_TIME_SCRIPT).toString()));
if (!isHideDelay) {
// hide menu after testing @showDelay
getMetamerPage().getRequestTimeElement().click();
}
}
Number median = countMedian(delays);
assertEquals(median.doubleValue(), expectedDelayInMillis, tolerance, "The delay is not in tolerance. Median of"
+ " delays was " + median);
}
public void testHideDelay(final WebElement menuRootElement, final long expectedDelayInMillis, Event[] triggerEvents, WebElement triggerEventOnElement) {
testDelay(Boolean.TRUE, menuRootElement, expectedDelayInMillis, triggerEvents, triggerEventOnElement);
}
public void testHideDelay(final WebElement menuRootElement, final long expectedDelayInMillis, Event triggerEvent, WebElement triggerEventOnElement) {
testHideDelay(menuRootElement, expectedDelayInMillis, new Event[] { triggerEvent }, triggerEventOnElement);
}
public void testShowDelay(final WebElement menuRootElement, final long expectedDelayInMillis, Event[] triggerEvents, WebElement triggerEventOnElement) {
testDelay(Boolean.FALSE, menuRootElement, expectedDelayInMillis, triggerEvents, triggerEventOnElement);
}
public void testShowDelay(final WebElement menuRootElement, final long expectedDelayInMillis, Event triggerEvent, WebElement triggerEventOnElement) {
testDelay(Boolean.FALSE, menuRootElement, expectedDelayInMillis, new Event[] { triggerEvent }, triggerEventOnElement);
}
private class EventTriggeredPredicate implements Predicate<WebDriver> {
private final String eventName;
private String lastReturnedString;
private final String valueToEqualTo;
protected EventTriggeredPredicate(String eventName) {
this(eventName, "true");
}
protected EventTriggeredPredicate(String eventName, String valueToEqualTo) {
this.eventName = eventName;
this.valueToEqualTo = valueToEqualTo.toLowerCase();
}
@Override
public boolean apply(WebDriver t) {
lastReturnedString = executor.executeScript(format("return window.{0};", eventName)).toString().toLowerCase();
return lastReturnedString.equals(valueToEqualTo);
}
@Override
public String toString() {
return format("<{0}> to be equal to <{1}>, last returned value was <{2}>.", eventName, valueToEqualTo,
lastReturnedString);
}
}
}
private final TestResourcesProvider testResourcesProvider = new TestResourcesProvider() {
@Override
public UnsafeAttributes getAttributes(String attributeTableId) {
return getUnsafeAttributes(attributeTableId);
}
@Override
public JavascriptExecutor getJSExecutor() {
return executor;
}
@Override
public WebDriver getWebDriver() {
return driver;
}
};
public MultipleAttributesSetter attsSetter() {
return new AttributesHandler(testResourcesProvider);
}
@JavaScript("window.JSErrorStorage")
public interface JSErrorStorage {
List<String> getMessages();
}
}