/* * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl-2.1.html * * This library 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. * * Contributors: * <a href="mailto:grenard@nuxeo.com">Guillaume</a> */ package org.nuxeo.functionaltests.forms; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.functionaltests.AbstractTest; import org.nuxeo.functionaltests.Locator; import org.nuxeo.functionaltests.fragment.WebFragmentImpl; import org.nuxeo.functionaltests.pages.search.SearchPage; import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.support.ui.Wait; import com.gargoylesoftware.htmlunit.ElementNotFoundException; import com.google.common.base.Function; /** * Convenient class to handle a select2Widget. * * @since 5.7.3 */ public class Select2WidgetElement extends WebFragmentImpl { private static class Select2Wait implements Function<WebElement, Boolean> { public Boolean apply(WebElement element) { boolean result = !element.getAttribute("class").contains( S2_CSS_ACTIVE_CLASS); return result; } } private static final Log log = LogFactory.getLog(Select2WidgetElement.class); private static final String S2_CSS_ACTIVE_CLASS = "select2-active"; private static final String S2_MULTIPLE_CURRENT_SELECTION_XPATH = "ul[@class='select2-choices']/li[@class='select2-search-choice']"; private final static String S2_MULTIPLE_INPUT_XPATH = "ul/li/input"; private static final String S2_SINGLE_CURRENT_SELECTION_XPATH = "a[@class='select2-choice']/span[@class='select2-chosen']"; private final static String S2_SINGLE_INPUT_XPATH = "//*[@id='select2-drop']/div/input"; private static final String S2_SUGGEST_RESULT_XPATH = "//*[@id='select2-drop']//li[contains(@class,'select2-result-selectable')]/div"; /** * Select2 loading timeout in seconds. */ private static final int SELECT2_LOADING_TIMEOUT = 20; private boolean mutliple = false; /** * Constructor. * * @param driver the driver * @param by the by locator of the widget */ public Select2WidgetElement(WebDriver driver, WebElement element) { super(driver, element); } /** * Constructor. * * @param driver the driver * @param by the by locator of the widget * @param multiple whether the widget can have multiple values */ public Select2WidgetElement(final WebDriver driver, WebElement element, final boolean multiple) { this(driver, element); this.mutliple = multiple; } /** * @since 5.9.3 */ public WebElement getSelectedValue() { if (mutliple) { throw new UnsupportedOperationException( "The select2 is multiple and has multiple selected values"); } return element.findElement(By.xpath(S2_SINGLE_CURRENT_SELECTION_XPATH)); } /** * @since 5.9.3 */ public List<WebElement> getSelectedValues() { if (!mutliple) { throw new UnsupportedOperationException( "The select2 is not multiple and can't have multiple selected values"); } return element.findElements(By.xpath(S2_MULTIPLE_CURRENT_SELECTION_XPATH)); } /** * @since 5.9.3 */ protected String getSubmittedValue() { String eltId = element.getAttribute("id"); String submittedEltId = element.getAttribute("id").substring("s2id_".length(), eltId.length()); return driver.findElement(By.id(submittedEltId)).getAttribute("value"); } public List<WebElement> getSuggestedEntries() { try { return driver.findElements(By.xpath(S2_SUGGEST_RESULT_XPATH)); } catch (NoSuchElementException e) { return null; } } /** * @since 5.9.3 */ public void removeFromSelection(final String displayedText) { if (!mutliple) { throw new UnsupportedOperationException( "The select2 is not multiple and you can't remove a specific value"); } final String submittedValueBefore = getSubmittedValue(); boolean found =false; for (WebElement el : getSelectedValues()) { if (el.getText().equals(displayedText)) { el.findElement( By.xpath("a[@class='select2-search-choice-close']")).click(); found = true; } } if (found) { Locator.waitUntilGivenFunction(new Function<WebDriver, Boolean>() { public Boolean apply(WebDriver driver) { return !submittedValueBefore.equals(getSubmittedValue()); } }); } else { throw new ElementNotFoundException("remove link for select2 '" + displayedText + "' item", "", ""); } } /** * Select a single value. * * @param value the value to be selected * * @since 5.7.3 */ public void selectValue(final String value) { clickOnSelect2Field(); WebElement suggestInput = getSuggestInput(); char c; for (int i = 0; i < value.length(); i++) { c = value.charAt(i); suggestInput.sendKeys(c + ""); } waitSelect2(); if (getSuggestedEntries() != null && getSuggestedEntries().size() > 1) { log.warn("Suggestion for element " + element.getAttribute("id") + " returned more than 1 result, the first suggestion will be selected : " + getSuggestedEntries().get(0).getText()); } WebElement suggestion = driver.findElement(By.xpath(S2_SUGGEST_RESULT_XPATH)); suggestion.click(); } /** * Select multiple values. * * @param values the values * * @since 5.7.3 */ public void selectValues(final String[] values) { for (String value : values) { selectValue(value); } } /** * Type a value in the select2 and return the suggested entries. * * @param value The value to type in the select2. * @return The suggested values for the parameter. * * @since 6.0 */ public List<WebElement> typeAndGetResult(final String value) { clickOnSelect2Field(); WebElement suggestInput = getSuggestInput(); suggestInput.sendKeys(value); try { waitSelect2(); } catch (TimeoutException e) { log.warn("Suggestion timed out with input : " + value + ". Let's try with next letter."); } return getSuggestedEntries(); } /** * Click on the select2 field. * * @since 6.0 */ private void clickOnSelect2Field() { WebElement select2Field = null; if (mutliple) { select2Field = element; } else { select2Field = element.findElement(By.xpath("a[contains(@class,'select2-choice')]")); } select2Field.click(); } /** * @return The suggest input element. * * @since 6.0 */ private WebElement getSuggestInput() { WebElement suggestInput = null; if (mutliple) { suggestInput = element.findElement(By.xpath("ul/li[@class='select2-search-field']/input")); } else { suggestInput = driver.findElement(By.xpath(S2_SINGLE_INPUT_XPATH)); } return suggestInput; } /** * Do a wait on the select2 field. * * @throws TimeoutException * * @since 6.0 */ private void waitSelect2() throws TimeoutException { Wait<WebElement> wait = new FluentWait<WebElement>( !mutliple ? driver.findElement(By.xpath(S2_SINGLE_INPUT_XPATH)) : element.findElement(By.xpath(S2_MULTIPLE_INPUT_XPATH))).withTimeout( SELECT2_LOADING_TIMEOUT, TimeUnit.SECONDS).pollingEvery( 100, TimeUnit.MILLISECONDS).ignoring( NoSuchElementException.class); Function<WebElement, Boolean> s2WaitFunction = new Select2Wait(); wait.until(s2WaitFunction); } /** * Clear the input of the select2. * * @since 6.0 */ public void clearSuggestInput() { WebElement suggestInput = null; if (mutliple) { suggestInput = driver.findElement(By.xpath("//ul/li[@class='select2-search-field']/input")); } else { suggestInput = driver.findElement(By.xpath(S2_SINGLE_INPUT_XPATH)); } if (suggestInput != null) { suggestInput.clear(); } } /** * Click on the select2 element. * * @since 6.0 */ public void clickSelect2Field() { WebElement select2Field = null; if (mutliple) { select2Field = element; } else { select2Field = element.findElement(By.xpath("a[contains(@class,'select2-choice select2-default')]")); } select2Field.click(); } /** * Type a value in the select2 and then simulate the enter key. * * @since 6.0 */ public SearchPage typeValueAndTypeEnter(String value) { clickOnSelect2Field(); WebElement suggestInput = getSuggestInput(); suggestInput.sendKeys(value); try { waitSelect2(); } catch (TimeoutException e) { log.warn("Suggestion timed out with input : " + value + ". Let's try with next letter."); } suggestInput.sendKeys(Keys.RETURN); return AbstractTest.asPage(SearchPage.class); } }